├── .dex-comparisons ├── .gitignore ├── .gitmodules ├── .husky └── pre-commit ├── .npmignore ├── 4naly3er-report.md ├── 512x512-MF.png ├── LICENSE.txt ├── README.md ├── basin(green)-512x512 (1).png ├── basin(green)-512x512.png ├── basin.pdf ├── discord-export ├── Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html ├── Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files │ ├── 0-EB806.png │ ├── 0170e083c6b7b4b253ce74396a594013-2CF6E.png │ ├── 021eb22c1e54e7d6b7976608d2a09da4-3028E.png │ ├── 059d0fe5cf95abe78825804be97230dc-0FE6E.png │ ├── 074b45b02c7fe972a78a7939d12f7367-39D75.png │ ├── 0ab6fddedfdca4dee0b54ab41ab4faad-3EFDA.png │ ├── 1237352656457043998-78A28.png │ ├── 1237582204905979995-AD6AE.png │ ├── 14ea78b80318f2bf28da987fc2be9912-F4E16.png │ ├── 1f389-5C738.svg │ ├── 1f3c1-445DC.svg │ ├── 1f3c5-82E3F.svg │ ├── 1f43a-EB486.svg │ ├── 1f440-6C64D.svg │ ├── 1f446-9CC34.svg │ ├── 1f44b-8A059.svg │ ├── 1f44d-1f3fb-ED2AA.svg │ ├── 1f44d-27259.svg │ ├── 1f4aa-2FD27.svg │ ├── 1f4af-4CFF5.svg │ ├── 1f4b8-E3468.svg │ ├── 1f4c6-44E30.svg │ ├── 1f50d-195C0.svg │ ├── 1f525-8FE4F.svg │ ├── 1f614-BB5EE.svg │ ├── 1f61d-6A1D5.svg │ ├── 1f642-83E8A.svg │ ├── 1f64c-1f3fb-01614.svg │ ├── 1f64f-22B8D.svg │ ├── 1f6a8-A8AB3.svg │ ├── 1f911-F346C.svg │ ├── 1f914-15707.svg │ ├── 1f916-AD810.svg │ ├── 1fae1-B19DE.svg │ ├── 2-ADBB4.png │ ├── 26a0-D845B.svg │ ├── 2705-0589F.svg │ ├── 2764-A3D25.svg │ ├── 281c37c156e582a637fd070f083d02db-ACBCC.png │ ├── 300410401819ea00f31fbcdbf9f5080a-23405.png │ ├── 357510ed1e5265aafb02bc7942ddaa82-01F7E.png │ ├── 407f6dcef34069853b9ca2321a06fb98-1DD94.png │ ├── 453eac052c6c6b7a61d2f55848c5bdc6-FCF15.png │ ├── 4832bd21924fffa7b010e0bb57758aa2-E6DDC.png │ ├── 4eaf99d01e4b5042454b4a6a8809687a-330D1.png │ ├── 5215abf963d5747c38f10ffb2c034e41-02918.png │ ├── 546f74664414743e95af6c3045a13083-799B6.png │ ├── 55b9ac870fd4a1b4fc5a8c0550c27aa4-D1820.png │ ├── 5b71d15a9bcde45fd5520e3fb580eb53-E1A50.png │ ├── 62a9e9fdf868396a9674f71e04ffab94-4A6BA.png │ ├── 6678d08791c986738fd9018c9cb8212b-D895F.png │ ├── 67594ee4b4d1fc03bca468327a0d145b-BD76A.png │ ├── 701300a5bdd3bf5640bd1370251533e2-0DC47.png │ ├── 7194c28496e51d0a5c8e9b15e2ffcb3b-8BFA7.png │ ├── 759305215cc64ed169073b720e944acc-B2606.png │ ├── 78025b51fbd5016bf80d568e3b199528-E03F8.png │ ├── 970d2e2f00cd7ef2134a1a3f21326349-404EA.png │ ├── Beanstalk-BC9BA │ ├── Screenshot_2024-08-14_at_12.01.40_PM-19370.png │ ├── Screenshot_2024-08-14_at_12.15.01_PM-1D17E.png │ ├── a476320e0ef8160f4704597ba8e9b4b8-ECB13.png │ ├── ac37f50421d713952c5567868c0ce8fc-83194.png │ ├── bdd37d345b2d35b5513f03a81143aa4a-51209.png │ ├── c9cb30134c634c9e02d0c64df4922803-98E33.png │ ├── curve-stablecoin-9DF7A │ ├── d8e05392a6dee96280405f9825554ff7-54BEB.png │ ├── fc008e9401593faa330c1e49ff785e7a-1856A.png │ ├── ggsans-italic-400-E988B.woff2 │ ├── ggsans-italic-500-0777F.woff2 │ ├── ggsans-italic-600-CB411.woff2 │ ├── ggsans-italic-700-891AC.woff2 │ ├── ggsans-italic-800-D36B0.woff2 │ ├── ggsans-normal-400-1456D.woff2 │ ├── ggsans-normal-500-89CE5.woff2 │ ├── ggsans-normal-600-C1EA8.woff2 │ ├── ggsans-normal-700-1949A.woff2 │ ├── ggsans-normal-800-58487.woff2 │ ├── gjirlfriend-2FC5D.mp4 │ ├── gjirlfriend-A5592.mp4 │ ├── highlight.min-D8D27.js │ ├── i-am-back-estoy-de-vuelta-FC687.mp4 │ ├── image-64AA5.png │ ├── image-7F0F5.png │ ├── lottie.min-99657.js │ └── solarized-dark.min-BA98F.css ├── Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].txt └── Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].txt_Files │ ├── Beanstalk-BC9BA │ ├── Screenshot_2024-08-14_at_12.01.40_PM-19370.png │ ├── Screenshot_2024-08-14_at_12.15.01_PM-1D17E.png │ ├── curve-stablecoin-9DF7A │ ├── gjirlfriend-729B5.png │ ├── gjirlfriend-A5592.mp4 │ ├── i-am-back-estoy-de-vuelta-721B0.png │ ├── image-64AA5.png │ └── image-7F0F5.png ├── foundry.toml ├── mocks ├── functions │ ├── MockEmptyFunction.sol │ └── MockFunctionBad.sol ├── pumps │ ├── MockFailPump.sol │ └── MockPump.sol ├── tokens │ ├── MockToken.sol │ ├── MockTokenFeeOnTransfer.sol │ ├── MockTokenNoName.sol │ └── ReentrantMockToken.sol └── wells │ ├── MockInitFailWell.sol │ ├── MockReserveWell.sol │ ├── MockStaticWell.sol │ └── MockWellUpgradeable.sol ├── multi-flow-pump.pdf ├── out_of_scope.txt ├── package.json ├── remappings.txt ├── requirements.txt ├── scope.txt ├── script ├── bin │ ├── deploy-local.sh │ ├── doc.sh │ └── env.sh ├── deploy │ ├── Aquifer.s.sol │ ├── AquiferWell.s.sol │ ├── MockPump.s.sol │ ├── Well.s.sol │ └── helpers │ │ └── Logger.sol ├── dex-comparisons.js ├── helpers │ └── WellDeployer.sol └── simulations │ └── stableswap │ ├── StableswapCalcRatiosLiqSim.s.sol │ └── StableswapCalcRatiosSwapSim.s.sol ├── src ├── Aquifer.sol ├── Well.sol ├── WellUpgradeable.sol ├── functions │ ├── ConstantProduct.sol │ ├── ConstantProduct2.sol │ ├── ProportionalLPToken.sol │ ├── ProportionalLPToken2.sol │ ├── Stable2.sol │ └── StableLUT │ │ └── Stable2LUT1.sol ├── interfaces │ ├── IAquifer.sol │ ├── IBeanstalkWellFunction.sol │ ├── ILookupTable.sol │ ├── IMultiFlowPumpWellFunction.sol │ ├── IWell.sol │ ├── IWellErrors.sol │ ├── IWellFunction.sol │ └── pumps │ │ ├── ICumulativePump.sol │ │ ├── IInstantaneousPump.sol │ │ ├── IMultiFlowPumpErrors.sol │ │ └── IPump.sol ├── libraries │ ├── ABDKMathQuad.sol │ ├── LibBytes.sol │ ├── LibBytes16.sol │ ├── LibClone.sol │ ├── LibContractInfo.sol │ ├── LibLastReserveBytes.sol │ ├── LibMath.sol │ ├── LibWellConstructor.sol │ └── LibWellUpgradeableConstructor.sol ├── pumps │ └── MultiFlowPump.sol └── utils │ ├── Clone.sol │ └── ClonePlus.sol ├── test ├── Aquifer.t.sol ├── LiquidityHelper.sol ├── Stable2 │ ├── LookupTable.t.sol │ ├── Well.Stable2.AddLiquidity.t.sol │ ├── Well.Stable2.Bore.t.sol │ ├── Well.Stable2.RemoveLiquidity.t.sol │ ├── Well.Stable2.RemoveLiquidityImbalanced.t.sol │ ├── Well.Stable2.RemoveLiquidityOneToken.t.sol │ ├── Well.Stable2.Shift.t.sol │ ├── Well.Stable2.Skim.t.sol │ ├── Well.Stable2.SwapFrom.t.sol │ └── Well.Stable2.SwapTo.t.sol ├── SwapHelper.sol ├── TestHelper.sol ├── Well.AddLiquidity.t.sol ├── Well.AddLiquidityFeeOnTransfer.Fee.t.sol ├── Well.AddLiquidityFeeOnTransfer.NoFee.t.sol ├── Well.Bore.t.sol ├── Well.DuplicateTokens.t.sol ├── Well.FeeOnTransfer.t.sol ├── Well.ReadOnlyReentrancy.t.sol ├── Well.RemoveLiquidity.t.sol ├── Well.RemoveLiquidityImbalanced.t.sol ├── Well.RemoveLiquidityOneToken.t.sol ├── Well.Shift.t.sol ├── Well.Skim.t.sol ├── Well.SucceedOnPumpFailure.t.sol ├── Well.SwapFrom.t.sol ├── Well.SwapFromFeeOnTransfer.Fee.t.sol ├── Well.SwapFromFeeOnTransfer.NoFee.t.sol ├── Well.SwapTo.t.sol ├── Well.Sync.t.sol ├── Well.Tokens.t.sol ├── Well.UpdatePump.t.sol ├── WellUpgradeable.t.sol ├── beanstalk │ ├── BeanstalkConstantProduct.calcReserveAtRatioLiquidity.t.sol │ ├── BeanstalkConstantProduct.calcReserveAtRatioSwap.t.sol │ ├── BeanstalkConstantProduct2.calcReserveAtRatioLiquidity.t.sol │ ├── BeanstalkConstantProduct2.calcReserveAtRatioSwap.t.sol │ ├── BeanstalkStable2.calcReserveAtRatioLiquidity.t.sol │ └── BeanstalkStable2.calcReserveAtRatioSwap.t.sol ├── differential │ ├── ConstantProduct.py │ ├── cap_reserves.py │ └── powu.py ├── functions │ ├── ConstantProduct.t.sol │ ├── ConstantProduct2.t.sol │ ├── Stable2.t.sol │ └── WellFunctionHelper.sol ├── helpers │ └── Users.sol ├── integration │ ├── GasMetering.sol │ ├── IntegrationTestGasComparisons.sol │ ├── IntegrationTestHelper.sol │ └── interfaces │ │ ├── ICurve.sol │ │ ├── IPipeline.sol │ │ └── IUniswap.sol ├── invariant │ ├── Handler.t.sol │ └── Invariants.t.sol ├── libraries │ ├── LibBytes.t.sol │ ├── LibBytes16.t.sol │ ├── LibContractInfo.t.sol │ ├── LibLastReserveBytes.t.sol │ ├── LibMath.t.sol │ └── TestABDK.t.sol └── pumps │ ├── Pump.CapReserves.t.sol │ ├── Pump.Fuzz.t.sol │ ├── Pump.Helpers.t.sol │ ├── Pump.Longevity.t.sol │ ├── Pump.NotInitialized.t.sol │ ├── Pump.TimeWeightedAverage.t.sol │ ├── Pump.Update.t.sol │ ├── PumpHelpers.sol │ └── simulate.py └── yarn.lock /.dex-comparisons: -------------------------------------------------------------------------------- 1 | IntegrationTestGasComparisons:testFuzz_uniswapV2_WethDaiUsdc_Swap(uint256) (runs: 5000, μ: 158307, ~: 158307) 2 | IntegrationTestGasComparisons:testFuzz_uniswapV2_WethDai_AddLiquidity(uint256) (runs: 5000, μ: 102974, ~: 102974) 3 | IntegrationTestGasComparisons:testFuzz_uniswapV2_WethDai_RemoveLiquidity(uint256) (runs: 5000, μ: 41103, ~: 41125) 4 | IntegrationTestGasComparisons:testFuzz_uniswapV2_WethDai_Swap(uint256) (runs: 5000, μ: 88314, ~: 88314) 5 | IntegrationTestGasComparisons:testFuzz_uniswapV3_WethDaiUsdc_Swap(uint256) (runs: 5000, μ: 815083, ~: 931705) 6 | IntegrationTestGasComparisons:testFuzz_uniswapV3_WethDai_Swap(uint256) (runs: 5000, μ: 297519, ~: 369033) 7 | IntegrationTestGasComparisons:testFuzz_wells_WethDaiUsdc_Shift(uint256) (runs: 5000, μ: 145708, ~: 145720) 8 | IntegrationTestGasComparisons:testFuzz_wells_WethDaiUsdc_Swap(uint256) (runs: 5000, μ: 311285, ~: 311280) 9 | IntegrationTestGasComparisons:testFuzz_wells_WethDai_AddLiquidity(uint256) (runs: 5000, μ: 137449, ~: 137443) 10 | IntegrationTestGasComparisons:testFuzz_wells_WethDai_RemoveLiquidity(uint256) (runs: 5000, μ: 108617, ~: 109640) 11 | IntegrationTestGasComparisons:testFuzz_wells_WethDai_Swap(uint256) (runs: 5000, μ: 122079, ~: 122283) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiler files 2 | cache/ 3 | out/ 4 | 5 | .vscode 6 | 7 | # Ignores development broadcast logs 8 | /broadcast 9 | 10 | # Dotenv file 11 | .env 12 | 13 | # Ignore generated CSV with DEX gas comparisons 14 | .dex-comparisons.csv 15 | 16 | # Other 17 | node_modules 18 | stash 19 | lcov.info 20 | .DS_Store 21 | test/output/ 22 | 23 | # Python 24 | __pycache__ -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/prb-math"] 2 | path = lib/prb-math 3 | url = https://github.com/paulrberg/prb-math 4 | branch = v3 5 | [submodule "lib/openzeppelin-contracts"] 6 | path = lib/openzeppelin-contracts 7 | url = https://github.com/OpenZeppelin/openzeppelin-contracts 8 | branch = v4.9.3 9 | [submodule "lib/openzeppelin-contracts-upgradeable"] 10 | path = lib/openzeppelin-contracts-upgradeable 11 | url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable 12 | branch = v4.9.3 13 | [submodule "lib/forge-std"] 14 | path = lib/forge-std 15 | url = https://github.com/foundry-rs/forge-std 16 | branch = v1.5.5 17 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | forge fmt --check 5 | 6 | npx lint-staged 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Compiler files 2 | cache/ 3 | 4 | # Ignores development broadcast logs 5 | /broadcast 6 | 7 | # Dotenv file 8 | .env 9 | 10 | # Ignore generated CSV with DEX gas comparisons 11 | .dex-comparisons.csv 12 | 13 | # Other 14 | node_modules 15 | stash 16 | lcov.info 17 | .DS_Store 18 | test/output/ 19 | 20 | 21 | # Npm ignored files 22 | test 23 | .github 24 | .husky -------------------------------------------------------------------------------- /512x512-MF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/512x512-MF.png -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023-present Publius 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /basin(green)-512x512 (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/basin(green)-512x512 (1).png -------------------------------------------------------------------------------- /basin(green)-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/basin(green)-512x512.png -------------------------------------------------------------------------------- /basin.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/basin.pdf -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/0-EB806.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/0-EB806.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/0170e083c6b7b4b253ce74396a594013-2CF6E.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/0170e083c6b7b4b253ce74396a594013-2CF6E.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/021eb22c1e54e7d6b7976608d2a09da4-3028E.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/021eb22c1e54e7d6b7976608d2a09da4-3028E.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/059d0fe5cf95abe78825804be97230dc-0FE6E.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/059d0fe5cf95abe78825804be97230dc-0FE6E.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/074b45b02c7fe972a78a7939d12f7367-39D75.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/074b45b02c7fe972a78a7939d12f7367-39D75.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/0ab6fddedfdca4dee0b54ab41ab4faad-3EFDA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/0ab6fddedfdca4dee0b54ab41ab4faad-3EFDA.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/1237352656457043998-78A28.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/1237352656457043998-78A28.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/1237582204905979995-AD6AE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/1237582204905979995-AD6AE.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/14ea78b80318f2bf28da987fc2be9912-F4E16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/14ea78b80318f2bf28da987fc2be9912-F4E16.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/1f389-5C738.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/1f3c1-445DC.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/1f3c5-82E3F.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/1f43a-EB486.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/1f440-6C64D.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/1f446-9CC34.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/1f44b-8A059.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/1f44d-1f3fb-ED2AA.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/1f44d-27259.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/1f4aa-2FD27.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/1f4af-4CFF5.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/1f4c6-44E30.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/1f50d-195C0.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/1f525-8FE4F.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/1f614-BB5EE.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/1f61d-6A1D5.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/1f642-83E8A.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/1f64c-1f3fb-01614.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/1f64f-22B8D.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/1f6a8-A8AB3.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/1f911-F346C.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/1f914-15707.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/1f916-AD810.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/1fae1-B19DE.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/2-ADBB4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/2-ADBB4.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/26a0-D845B.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/2705-0589F.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/2764-A3D25.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/281c37c156e582a637fd070f083d02db-ACBCC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/281c37c156e582a637fd070f083d02db-ACBCC.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/300410401819ea00f31fbcdbf9f5080a-23405.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/300410401819ea00f31fbcdbf9f5080a-23405.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/357510ed1e5265aafb02bc7942ddaa82-01F7E.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/357510ed1e5265aafb02bc7942ddaa82-01F7E.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/407f6dcef34069853b9ca2321a06fb98-1DD94.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/407f6dcef34069853b9ca2321a06fb98-1DD94.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/453eac052c6c6b7a61d2f55848c5bdc6-FCF15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/453eac052c6c6b7a61d2f55848c5bdc6-FCF15.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/4832bd21924fffa7b010e0bb57758aa2-E6DDC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/4832bd21924fffa7b010e0bb57758aa2-E6DDC.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/4eaf99d01e4b5042454b4a6a8809687a-330D1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/4eaf99d01e4b5042454b4a6a8809687a-330D1.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/5215abf963d5747c38f10ffb2c034e41-02918.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/5215abf963d5747c38f10ffb2c034e41-02918.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/546f74664414743e95af6c3045a13083-799B6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/546f74664414743e95af6c3045a13083-799B6.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/55b9ac870fd4a1b4fc5a8c0550c27aa4-D1820.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/55b9ac870fd4a1b4fc5a8c0550c27aa4-D1820.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/5b71d15a9bcde45fd5520e3fb580eb53-E1A50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/5b71d15a9bcde45fd5520e3fb580eb53-E1A50.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/62a9e9fdf868396a9674f71e04ffab94-4A6BA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/62a9e9fdf868396a9674f71e04ffab94-4A6BA.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/6678d08791c986738fd9018c9cb8212b-D895F.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/6678d08791c986738fd9018c9cb8212b-D895F.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/67594ee4b4d1fc03bca468327a0d145b-BD76A.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/67594ee4b4d1fc03bca468327a0d145b-BD76A.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/701300a5bdd3bf5640bd1370251533e2-0DC47.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/701300a5bdd3bf5640bd1370251533e2-0DC47.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/7194c28496e51d0a5c8e9b15e2ffcb3b-8BFA7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/7194c28496e51d0a5c8e9b15e2ffcb3b-8BFA7.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/759305215cc64ed169073b720e944acc-B2606.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/759305215cc64ed169073b720e944acc-B2606.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/78025b51fbd5016bf80d568e3b199528-E03F8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/78025b51fbd5016bf80d568e3b199528-E03F8.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/970d2e2f00cd7ef2134a1a3f21326349-404EA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/970d2e2f00cd7ef2134a1a3f21326349-404EA.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/Beanstalk-BC9BA: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/Beanstalk-BC9BA -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/Screenshot_2024-08-14_at_12.01.40_PM-19370.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/Screenshot_2024-08-14_at_12.01.40_PM-19370.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/Screenshot_2024-08-14_at_12.15.01_PM-1D17E.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/Screenshot_2024-08-14_at_12.15.01_PM-1D17E.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/a476320e0ef8160f4704597ba8e9b4b8-ECB13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/a476320e0ef8160f4704597ba8e9b4b8-ECB13.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/ac37f50421d713952c5567868c0ce8fc-83194.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/ac37f50421d713952c5567868c0ce8fc-83194.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/bdd37d345b2d35b5513f03a81143aa4a-51209.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/bdd37d345b2d35b5513f03a81143aa4a-51209.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/c9cb30134c634c9e02d0c64df4922803-98E33.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/c9cb30134c634c9e02d0c64df4922803-98E33.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/curve-stablecoin-9DF7A: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/curve-stablecoin-9DF7A -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/d8e05392a6dee96280405f9825554ff7-54BEB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/d8e05392a6dee96280405f9825554ff7-54BEB.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/fc008e9401593faa330c1e49ff785e7a-1856A.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/fc008e9401593faa330c1e49ff785e7a-1856A.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/ggsans-italic-400-E988B.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/ggsans-italic-400-E988B.woff2 -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/ggsans-italic-500-0777F.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/ggsans-italic-500-0777F.woff2 -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/ggsans-italic-600-CB411.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/ggsans-italic-600-CB411.woff2 -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/ggsans-italic-700-891AC.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/ggsans-italic-700-891AC.woff2 -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/ggsans-italic-800-D36B0.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/ggsans-italic-800-D36B0.woff2 -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/ggsans-normal-400-1456D.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/ggsans-normal-400-1456D.woff2 -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/ggsans-normal-500-89CE5.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/ggsans-normal-500-89CE5.woff2 -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/ggsans-normal-600-C1EA8.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/ggsans-normal-600-C1EA8.woff2 -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/ggsans-normal-700-1949A.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/ggsans-normal-700-1949A.woff2 -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/ggsans-normal-800-58487.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/ggsans-normal-800-58487.woff2 -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/gjirlfriend-2FC5D.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/gjirlfriend-2FC5D.mp4 -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/gjirlfriend-A5592.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/gjirlfriend-A5592.mp4 -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/i-am-back-estoy-de-vuelta-FC687.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/i-am-back-estoy-de-vuelta-FC687.mp4 -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/image-64AA5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/image-64AA5.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/image-7F0F5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/image-7F0F5.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].html_Files/solarized-dark.min-BA98F.css: -------------------------------------------------------------------------------- 1 | .hljs{display:block;overflow-x:auto;padding:.5em;background:#002b36;color:#839496}.hljs-comment,.hljs-quote{color:#586e75}.hljs-keyword,.hljs-selector-tag,.hljs-addition{color:#859900}.hljs-number,.hljs-string,.hljs-meta .hljs-meta-string,.hljs-literal,.hljs-doctag,.hljs-regexp{color:#2aa198}.hljs-title,.hljs-section,.hljs-name,.hljs-selector-id,.hljs-selector-class{color:#268bd2}.hljs-attribute,.hljs-attr,.hljs-variable,.hljs-template-variable,.hljs-class .hljs-title,.hljs-type{color:#b58900}.hljs-symbol,.hljs-bullet,.hljs-subst,.hljs-meta,.hljs-meta .hljs-keyword,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-link{color:#cb4b16}.hljs-built_in,.hljs-deletion{color:#dc322f}.hljs-formula{background:#073642}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:bold} -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].txt_Files/Beanstalk-BC9BA: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].txt_Files/Beanstalk-BC9BA -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].txt_Files/Screenshot_2024-08-14_at_12.01.40_PM-19370.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].txt_Files/Screenshot_2024-08-14_at_12.01.40_PM-19370.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].txt_Files/Screenshot_2024-08-14_at_12.15.01_PM-1D17E.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].txt_Files/Screenshot_2024-08-14_at_12.15.01_PM-1D17E.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].txt_Files/curve-stablecoin-9DF7A: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].txt_Files/curve-stablecoin-9DF7A -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].txt_Files/gjirlfriend-729B5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].txt_Files/gjirlfriend-729B5.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].txt_Files/gjirlfriend-A5592.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].txt_Files/gjirlfriend-A5592.mp4 -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].txt_Files/i-am-back-estoy-de-vuelta-721B0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].txt_Files/i-am-back-estoy-de-vuelta-721B0.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].txt_Files/image-64AA5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].txt_Files/image-64AA5.png -------------------------------------------------------------------------------- /discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].txt_Files/image-7F0F5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/discord-export/Code4rena - ARCHIVE-Q3-2024 - basin-jul29 [1265692874297839746].txt_Files/image-7F0F5.png -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = 'src' 3 | out = 'out' 4 | libs = ['lib', 'node_modules'] 5 | fuzz = { runs = 256 } 6 | optimizer = true 7 | optimizer_runs = 400 8 | remappings = [ 9 | '@openzeppelin/=node_modules/@openzeppelin/', 10 | ] 11 | block_number = 16826654 12 | block_timestamp = 1678803642 13 | 14 | [rpc_endpoints] 15 | mainnet = "${MAINNET_RPC_URL}" 16 | 17 | [profile.ci] 18 | no_match_test = "testSim" 19 | fuzz = { runs = 5_000, max_test_rejects = 1000000 } 20 | 21 | [profile.sim] 22 | match_test = "testSim" 23 | fuzz = { runs = 5_000, max_test_rejects = 1000000 } 24 | 25 | [fmt] 26 | ignore = ["src/libraries/LibClone.sol", "src/utils/Clone.sol", "src/libraries/ABDKMathQuad.sol"] 27 | int_types = "long" 28 | line_length = 120 29 | multiline_func_header = "params_first" 30 | number_underscore = "thousands" 31 | override_spacing = false 32 | quote_style = "double" 33 | tab_width = 4 34 | # See more config options https://github.com/foundry-rs/foundry/tree/master/config 35 | 36 | [invariant] 37 | runs = 50 38 | depth = 500 39 | fail_on_revert = true -------------------------------------------------------------------------------- /mocks/functions/MockEmptyFunction.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * SPDX-License-Identifier: MIT 3 | */ 4 | 5 | pragma solidity ^0.8.20; 6 | 7 | import {IWellFunction} from "src/interfaces/IWellFunction.sol"; 8 | 9 | contract MockEmptyFunction is IWellFunction { 10 | function calcReserve(uint256[] memory, uint256, uint256, bytes calldata) external pure returns (uint256 d) { 11 | return 1; 12 | } 13 | 14 | function calcLpTokenSupply(uint256[] memory, bytes calldata) external pure returns (uint256 xj) { 15 | return 1; 16 | } 17 | 18 | function calcLPTokenUnderlying( 19 | uint256, 20 | uint256[] memory, 21 | uint256, 22 | bytes calldata 23 | ) external pure returns (uint256[] memory underlyingAmounts) { 24 | return underlyingAmounts; 25 | } 26 | 27 | function name() external pure override returns (string memory) {} 28 | 29 | function symbol() external pure override returns (string memory) {} 30 | } 31 | -------------------------------------------------------------------------------- /mocks/functions/MockFunctionBad.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * SPDX-License-Identifier: MIT 3 | */ 4 | 5 | pragma solidity ^0.8.20; 6 | 7 | import {IWellFunction} from "src/interfaces/IWellFunction.sol"; 8 | 9 | /** 10 | * @dev Implements a mock broken WellFunction implementation. 11 | * 12 | * Used to verify that {Well.getSwap} throws an error when a Well function 13 | * returns a reserve that is higher than Well reserves. 14 | * 15 | * DO NOT COPY IN PRODUCTION. 16 | */ 17 | contract MockFunctionBad is IWellFunction { 18 | function calcLpTokenSupply( 19 | uint256[] memory reserves, 20 | bytes calldata 21 | ) external pure returns (uint256 lpTokenSupply) { 22 | return reserves[0] + reserves[1]; 23 | } 24 | 25 | /// @dev returns non-zero regardless of reserves & lp token supply. WRONG! 26 | function calcReserve(uint256[] memory, uint256, uint256, bytes calldata) external pure returns (uint256 reserve) { 27 | return 1000; 28 | } 29 | 30 | function calcLPTokenUnderlying( 31 | uint256, 32 | uint256[] memory, 33 | uint256, 34 | bytes calldata 35 | ) external pure returns (uint256[] memory underlyingAmounts) { 36 | return underlyingAmounts; 37 | } 38 | 39 | function name() external pure override returns (string memory) {} 40 | 41 | function symbol() external pure override returns (string memory) {} 42 | } 43 | -------------------------------------------------------------------------------- /mocks/pumps/MockFailPump.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * SPDX-License-Identifier: MIT 3 | */ 4 | 5 | pragma solidity ^0.8.20; 6 | 7 | import "src/interfaces/pumps/IPump.sol"; 8 | 9 | /** 10 | * @title Mock Pump with a failing update function 11 | */ 12 | contract MockFailPump is IPump { 13 | function update(uint256[] calldata, bytes calldata) external pure { 14 | revert(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /mocks/pumps/MockPump.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * SPDX-License-Identifier: MIT 3 | */ 4 | 5 | pragma solidity ^0.8.20; 6 | 7 | import "src/interfaces/pumps/IPump.sol"; 8 | 9 | /** 10 | * @author Brendan 11 | * @title Mock Pump 12 | */ 13 | contract MockPump is IPump { 14 | bytes public lastData; 15 | 16 | function update(uint256[] calldata, bytes calldata data) external { 17 | lastData = data; 18 | } 19 | 20 | function read(address, bytes calldata) external view returns (bytes memory data) { 21 | return lastData; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /mocks/tokens/MockToken.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * SPDX-License-Identifier: MIT 3 | */ 4 | 5 | pragma solidity ^0.8.20; 6 | 7 | import "oz/token/ERC20/extensions/ERC20Burnable.sol"; 8 | import "oz/token/ERC20/extensions/draft-ERC20Permit.sol"; 9 | 10 | /** 11 | * @author Brendan 12 | * @title Mock Token 13 | */ 14 | contract MockToken is ERC20Burnable, ERC20Permit { 15 | uint8 private _decimals = 18; 16 | 17 | constructor(string memory name, string memory symbol, uint8 __decimals) ERC20(name, symbol) ERC20Permit(name) { 18 | _decimals = __decimals; 19 | } 20 | 21 | function mint(address account, uint256 amount) external returns (bool) { 22 | _mint(account, amount); 23 | return true; 24 | } 25 | 26 | function burnFrom(address account, uint256 amount) public override(ERC20Burnable) { 27 | ERC20Burnable.burnFrom(account, amount); 28 | } 29 | 30 | function burn(uint256 amount) public override(ERC20Burnable) { 31 | ERC20Burnable.burn(amount); 32 | } 33 | 34 | function decimals() public view virtual override returns (uint8) { 35 | return _decimals; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /mocks/tokens/MockTokenFeeOnTransfer.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * SPDX-License-Identifier: MIT 3 | */ 4 | 5 | pragma solidity ^0.8.20; 6 | 7 | import "oz/token/ERC20/extensions/ERC20Burnable.sol"; 8 | import "oz/token/ERC20/extensions/draft-ERC20Permit.sol"; 9 | 10 | /** 11 | * @author Brendan 12 | * @title Mock Token with a Fee on transfer 13 | */ 14 | contract MockTokenFeeOnTransfer is ERC20Burnable, ERC20Permit { 15 | uint8 private _decimals = 18; 16 | mapping(address => uint256) private _balances; 17 | 18 | uint256 constant FEE_DIVISOR = 1e18; 19 | 20 | uint256 public fee = 0; 21 | 22 | constructor(string memory name, string memory symbol, uint8 __decimals) ERC20(name, symbol) ERC20Permit(name) { 23 | _decimals = __decimals; 24 | } 25 | 26 | function mint(address account, uint256 amount) external returns (bool) { 27 | _mint(account, amount); 28 | return true; 29 | } 30 | 31 | function burnFrom(address account, uint256 amount) public override(ERC20Burnable) { 32 | ERC20Burnable.burnFrom(account, amount); 33 | } 34 | 35 | function burn(uint256 amount) public override(ERC20Burnable) { 36 | ERC20Burnable.burn(amount); 37 | } 38 | 39 | function decimals() public view virtual override returns (uint8) { 40 | return _decimals; 41 | } 42 | 43 | function setFee(uint256 _fee) external { 44 | fee = _fee; 45 | } 46 | 47 | function transfer(address to, uint256 amount) public virtual override returns (bool) { 48 | return __transfer(_msgSender(), to, amount); 49 | } 50 | 51 | function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) { 52 | address spender = _msgSender(); 53 | _spendAllowance(from, spender, amount); 54 | return __transfer(from, to, amount); 55 | } 56 | 57 | function __transfer(address from, address to, uint256 amount) internal returns (bool) { 58 | uint256 _fee = amount * fee / FEE_DIVISOR; 59 | uint256 amountSent = amount - _fee; 60 | 61 | _transfer(from, to, amountSent); 62 | _burn(from, _fee); 63 | 64 | return true; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /mocks/tokens/MockTokenNoName.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * SPDX-License-Identifier: MIT 3 | */ 4 | 5 | pragma solidity ^0.8.20; 6 | 7 | import "mocks/tokens/MockToken.sol"; 8 | 9 | /** 10 | * @author Brendan 11 | * @title Mock Token No Name 12 | * 13 | */ 14 | contract MockTokenNoName is MockToken { 15 | constructor(uint8 __decimals) MockToken("", "", __decimals) {} 16 | 17 | function name() public pure override returns (string memory) { 18 | revert(); 19 | } 20 | 21 | function symbol() public pure override returns (string memory) { 22 | revert(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /mocks/tokens/ReentrantMockToken.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * SPDX-License-Identifier: MIT 3 | */ 4 | 5 | pragma solidity ^0.8.20; 6 | 7 | import "mocks/tokens/MockToken.sol"; 8 | 9 | /** 10 | * @author Brendan 11 | * @title Reentrant Mock Token 12 | */ 13 | contract ReentrantMockToken is MockToken { 14 | address private target; 15 | bytes private callData; 16 | 17 | constructor(string memory name, string memory symbol, uint8 __decimals) MockToken(name, symbol, __decimals) {} 18 | 19 | function setCall(address _target, bytes calldata _callData) external { 20 | target = _target; 21 | callData = _callData; 22 | } 23 | 24 | function _beforeTokenTransfer(address, address, uint256) internal virtual override { 25 | if (target != address(0)) { 26 | (bool success, bytes memory data) = target.call(callData); 27 | if (!success) { 28 | if (data.length == 0) revert(); 29 | assembly { 30 | revert(add(32, data), mload(data)) 31 | } 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /mocks/wells/MockInitFailWell.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * SPDX-License-Identifier: MIT 3 | */ 4 | 5 | pragma solidity ^0.8.20; 6 | 7 | import {IPump} from "src/interfaces/pumps/IPump.sol"; 8 | 9 | /** 10 | * @notice Mock Well that fails on various init calls. 11 | */ 12 | contract MockInitFailWell { 13 | function initNoMessage() external pure { 14 | revert(); 15 | } 16 | 17 | function initMessage() external pure { 18 | revert("Well: fail message"); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /mocks/wells/MockReserveWell.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * SPDX-License-Identifier: MIT 3 | */ 4 | 5 | pragma solidity ^0.8.20; 6 | 7 | import {IPump} from "src/interfaces/pumps/IPump.sol"; 8 | import {Call} from "src/interfaces/IWell.sol"; 9 | 10 | /** 11 | * @notice Mock Well that allows setting of reserves. 12 | */ 13 | contract MockReserveWell { 14 | uint256[] reserves; 15 | Call _wellFunction; 16 | 17 | 18 | constructor() { 19 | reserves = new uint256[](2); 20 | } 21 | 22 | function setWellFunction(Call calldata __wellFunction) external { 23 | _wellFunction = __wellFunction; 24 | } 25 | 26 | function wellFunction() external view returns (Call memory) { 27 | return _wellFunction; 28 | } 29 | 30 | function setReserves(uint256[] memory _reserves) public { 31 | reserves = _reserves; 32 | } 33 | 34 | function getReserves() external view returns (uint256[] memory _reserves) { 35 | _reserves = reserves; 36 | } 37 | 38 | function update(address pump, uint256[] calldata _reserves, bytes calldata data) external { 39 | IPump(pump).update(reserves, data); 40 | setReserves(_reserves); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /mocks/wells/MockStaticWell.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * SPDX-License-Identifier: MIT 3 | */ 4 | 5 | pragma solidity ^0.8.20; 6 | 7 | import {console} from "test/TestHelper.sol"; 8 | import {ReentrancyGuardUpgradeable} from "ozu/security/ReentrancyGuardUpgradeable.sol"; 9 | import {IPump} from "src/interfaces/pumps/IPump.sol"; 10 | import {MockReserveWell} from "mocks/wells/MockReserveWell.sol"; 11 | import {ClonePlus} from "src/utils/ClonePlus.sol"; 12 | import {Call, IERC20} from "src/Well.sol"; 13 | 14 | /** 15 | * @title MockStaticWell 16 | * @author Silo Chad 17 | * @notice Simplified Well implementation which stores configuration in immutable 18 | * storage during construction. 19 | */ 20 | contract MockStaticWell is ReentrancyGuardUpgradeable, ClonePlus { 21 | address immutable TOKEN0; 22 | address immutable TOKEN1; 23 | address immutable WELL_FUNCTION_TARGET; 24 | bytes32 immutable WELL_FUNCTION_DATA; 25 | address immutable PUMP0_TARGET; 26 | bytes32 immutable PUMP0_DATA; 27 | address immutable AQUIFER; 28 | bytes32 immutable WELL_DATA; 29 | 30 | string public name; 31 | string public symbol; 32 | 33 | constructor( 34 | IERC20[] memory _tokens, 35 | Call memory _wellFunction, 36 | Call[] memory _pumps, 37 | address _aquifer, 38 | bytes memory _wellData 39 | ) { 40 | require(_tokens.length == 2, "MockStaticWell: invalid tokens"); 41 | require(_pumps.length == 1, "MockStaticWell: invalid pumps"); 42 | 43 | TOKEN0 = address(_tokens[0]); 44 | TOKEN1 = address(_tokens[1]); 45 | WELL_FUNCTION_TARGET = _wellFunction.target; 46 | WELL_FUNCTION_DATA = bytes32(_wellFunction.data); 47 | PUMP0_TARGET = _pumps[0].target; 48 | PUMP0_DATA = bytes32(_pumps[0].data); 49 | AQUIFER = _aquifer; 50 | WELL_DATA = bytes32(_wellData); 51 | } 52 | 53 | function init(string memory _name, string memory _symbol) public initializer { 54 | name = _name; 55 | symbol = _symbol; 56 | } 57 | 58 | function tokens() public view returns (IERC20[] memory _tokens) { 59 | _tokens = new IERC20[](2); 60 | _tokens[0] = IERC20(TOKEN0); 61 | _tokens[1] = IERC20(TOKEN1); 62 | } 63 | 64 | function wellFunction() public view returns (Call memory _wellFunction) { 65 | _wellFunction = Call(WELL_FUNCTION_TARGET, bytes32ToBytes(WELL_FUNCTION_DATA)); 66 | } 67 | 68 | function pumps() public view returns (Call[] memory _pumps) { 69 | _pumps = new Call[](1); 70 | _pumps[0] = Call(PUMP0_TARGET, bytes32ToBytes(PUMP0_DATA)); 71 | } 72 | 73 | function aquifer() public view returns (address) { 74 | return AQUIFER; 75 | } 76 | 77 | function wellData() public view returns (bytes memory) { 78 | return bytes32ToBytes(WELL_DATA); 79 | } 80 | 81 | /// @dev Read a uint256 off the front of immutable storage applied during Clone. 82 | /// Since the immutable variables defined above are instantiated during 83 | /// construction, their length is included in the offset; i.e., immutable 84 | /// vars added using Clone begin at index 0. See {Clone._getImmutableArgsOffset}. 85 | function immutableDataFromClone() public pure returns (uint256) { 86 | return _getArgUint256(0); 87 | } 88 | 89 | /// @dev Inefficient way to convert bytes32 back to bytes without padding 90 | function bytes32ToBytes(bytes32 data) internal pure returns (bytes memory) { 91 | uint256 i = 0; 92 | while (i < 32 && uint8(data[i]) != 0) { 93 | ++i; 94 | } 95 | bytes memory result = new bytes(i); 96 | i = 0; 97 | while (i < 32 && data[i] != 0) { 98 | result[i] = data[i]; 99 | ++i; 100 | } 101 | return result; 102 | } 103 | 104 | function isInitialized() external view returns (bool) { 105 | return _getInitializedVersion() > 0; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /mocks/wells/MockWellUpgradeable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.20; 4 | 5 | import {WellUpgradeable} from "src/WellUpgradeable.sol"; 6 | 7 | // this needs to be here for upgrade checks 8 | /// @custom:oz-upgrades-from WellUpgradeable 9 | contract MockWellUpgradeable is WellUpgradeable { 10 | 11 | function getVersion(uint256 i) external pure returns (uint256) { 12 | return i; 13 | } 14 | } -------------------------------------------------------------------------------- /multi-flow-pump.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-423n4/2024-07-basin/bbe3cafed46822b0d0b1e0b80f9dee623012bd92/multi-flow-pump.pdf -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@beanstalk/wells", 3 | "version": "1.2.0-prerelease0", 4 | "description": "A [{Well}](/src/Well.sol) is a constant function AMM that allows the provisioning of liquidity into a single pooled on-chain liquidity position.", 5 | "main": "index.js", 6 | "directories": { 7 | "lib": "lib", 8 | "test": "test" 9 | }, 10 | "scripts": { 11 | "prepare": "husky install", 12 | "t": ". ./script/bin/env.sh; forge t -vvv", 13 | "deploy:local": ". ./script/bin/env.sh; ./script/bin/deploy-local.sh", 14 | "compare": "forge snapshot --match-path test/integration/IntegrationTestGasComparisons.sol --snap .dex-comparisons", 15 | "compare:check": "yarn compare --check", 16 | "compare:csv": "node ./script/dex-comparisons.js", 17 | "test": "forge t -vvv --ffi" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/BeanstalkFarms/Wells.git" 22 | }, 23 | "keywords": [], 24 | "author": "", 25 | "license": "ISC", 26 | "bugs": { 27 | "url": "https://github.com/BeanstalkFarms/Wells/issues" 28 | }, 29 | "homepage": "https://github.com/BeanstalkFarms/Wells#readme", 30 | "devDependencies": { 31 | "husky": "8.0.3" 32 | }, 33 | "lint-staged": { 34 | "*.sol": "forge fmt" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /remappings.txt: -------------------------------------------------------------------------------- 1 | prb/math/=lib/prb-math/src/ 2 | forge-std/=lib/forge-std/src/ 3 | oz/=lib/openzeppelin-contracts/contracts/ 4 | ozu/=lib/openzeppelin-contracts-upgradeable/contracts/ 5 | src/=src/ 6 | mocks=mocks/ 7 | tests=tests/ 8 | utils=utils/ 9 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | cytoolz==0.12.3 2 | eth-hash==0.7.0 3 | eth-typing==3.5.2 4 | eth-utils==2.3.1 5 | eth_abi==5.1.0 6 | numpy==2.0.1 7 | pandas==2.2.2 8 | parsimonious==0.10.0 9 | python-dateutil==2.9.0.post0 10 | pytz==2024.1 11 | regex==2024.5.15 12 | setuptools==71.1.0 13 | six==1.16.0 14 | toolz==0.12.1 15 | typing_extensions==4.12.2 16 | tzdata==2024.1 17 | -------------------------------------------------------------------------------- /scope.txt: -------------------------------------------------------------------------------- 1 | ./src/functions/Stable2.sol 2 | ./src/functions/StableLUT/Stable2LUT1.sol 3 | ./src/WellUpgradeable.sol -------------------------------------------------------------------------------- /script/bin/deploy-local.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Private key for test account: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 4 | # NOTE: This is a test account provided by Hardhat/Forge for testing. It should 5 | # never be used in production. 6 | export PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" 7 | 8 | forge script "script/deploy/$1.s.sol:Deploy$1" \ 9 | --fork-url http://localhost:8545 \ 10 | --broadcast \ 11 | --sender "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266" -------------------------------------------------------------------------------- /script/bin/doc.sh: -------------------------------------------------------------------------------- 1 | docker run -v $(pwd):/Wells ethereum/solc:stable --base-path ./Wells $(tr '\n' ' ' < remappings.txt) -o /Wells/out --userdoc --devdoc --overwrite /Wells/src/Well.sol -------------------------------------------------------------------------------- /script/bin/env.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ## Usage: 4 | ## . ./export-env.sh ; $COMMAND 5 | ## . ./export-env.sh ; echo ${MINIENTREGA_FECHALIMITE} 6 | ## 7 | ## Source: 8 | ## https://stackoverflow.com/a/20909045 9 | ## Modified to surpress output of environment to stdout 10 | ### and to output a message if no .env file present. 11 | 12 | if [ ! -f '.env' ]; then 13 | 14 | echo "No .env file found, bypassing loading." 15 | return 0; 16 | 17 | fi 18 | 19 | unamestr=$(uname) 20 | if [ "$unamestr" = 'Linux' ]; then 21 | 22 | env $(grep -v '^#' .env | xargs -d '\n') &> /dev/null 23 | 24 | elif [ "$unamestr" = 'FreeBSD' ] || [ "$unamestr" = 'Darwin' ]; then 25 | 26 | env $(grep -v '^#' .env | xargs -0) &> /dev/null 27 | 28 | fi -------------------------------------------------------------------------------- /script/deploy/Aquifer.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.20; 3 | 4 | import {Script} from "forge-std/Script.sol"; 5 | import {Aquifer} from "src/Aquifer.sol"; 6 | 7 | // Script to deploy an {Aquifer}. 8 | // see {Aquifer}. 9 | contract DeployAquifer is Script { 10 | function run() external { 11 | uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); 12 | vm.startBroadcast(deployerPrivateKey); 13 | // Aquifer aquifer = new Aquifer(); 14 | vm.stopBroadcast(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /script/deploy/AquiferWell.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.20; 3 | 4 | import {Script, console} from "forge-std/Script.sol"; 5 | import {SafeERC20, IERC20} from "oz/token/ERC20/utils/SafeERC20.sol"; 6 | 7 | import {IWell, Call} from "src/interfaces/IWell.sol"; 8 | import {IWellFunction} from "src/interfaces/IWellFunction.sol"; 9 | import {IPump} from "src/interfaces/pumps/IPump.sol"; 10 | 11 | import {logger} from "script/deploy/helpers/Logger.sol"; 12 | import {MockToken} from "mocks/tokens/MockToken.sol"; 13 | import {MockPump} from "mocks/pumps/MockPump.sol"; 14 | 15 | import {ConstantProduct2} from "src/functions/ConstantProduct2.sol"; 16 | import {Well} from "src/Well.sol"; 17 | import {Aquifer} from "src/Aquifer.sol"; 18 | 19 | import {WellDeployer} from "script/helpers/WellDeployer.sol"; 20 | 21 | /** 22 | * @dev Script to deploy a BEAN-ETH {Well} with a ConstantProduct2 pricing function 23 | * and MockPump via an Aquifer. 24 | */ 25 | contract DeployAquiferWell is Script, WellDeployer { 26 | using SafeERC20 for IERC20; 27 | 28 | IERC20 constant BEAN = IERC20(0xBEA0000029AD1c77D3d5D23Ba2D8893dB9d1Efab); 29 | IERC20 constant WETH = IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); // WETH9 30 | 31 | function run() external { 32 | uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); 33 | vm.startBroadcast(deployerPrivateKey); 34 | 35 | // Tokens 36 | IERC20[] memory tokens = new IERC20[](2); 37 | tokens[0] = BEAN; 38 | tokens[1] = WETH; 39 | 40 | // Deploy Aquifer 41 | address aquifer = address(new Aquifer()); 42 | 43 | // Well Function 44 | IWellFunction cp2 = new ConstantProduct2(); 45 | Call memory wellFunction = Call(address(cp2), new bytes(0)); 46 | 47 | // Pump 48 | IPump mockPump = new MockPump(); 49 | Call[] memory pumps = new Call[](1); 50 | pumps[0] = Call(address(mockPump), new bytes(0)); 51 | 52 | // Well implementation 53 | address wellImplementation = address(new Well()); 54 | 55 | //bore well 56 | Well well = encodeAndBoreWell(aquifer, wellImplementation, tokens, wellFunction, pumps, bytes32(0)); 57 | 58 | console.log("Deployed CP2 at address: ", address(cp2)); 59 | console.log("Deployed Pump at address: ", address(pumps[0].target)); 60 | logger.logWell(well); 61 | 62 | vm.stopBroadcast(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /script/deploy/MockPump.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.20; 3 | 4 | import {Script, console} from "forge-std/Script.sol"; 5 | import {MockPump} from "mocks/pumps/MockPump.sol"; 6 | 7 | // Script to deploy a {MockPump}. 8 | // Mockpump does not provide utility and is solely used for example. 9 | contract DeployMockPump is Script { 10 | function run() external { 11 | uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); 12 | vm.startBroadcast(deployerPrivateKey); 13 | 14 | MockPump mockPump = new MockPump(); 15 | console.log("Deployed MockPump at address: ", address(mockPump)); 16 | 17 | vm.stopBroadcast(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /script/deploy/Well.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.20; 3 | 4 | import {console2} from "forge-std/console2.sol"; 5 | import {console} from "forge-std/console.sol"; 6 | import {Test} from "forge-std/Test.sol"; 7 | import {Script} from "forge-std/Script.sol"; 8 | 9 | import {WellDeployer} from "script/helpers/WellDeployer.sol"; 10 | import {logger} from "script/deploy/helpers/Logger.sol"; 11 | import {MockPump} from "mocks/pumps/MockPump.sol"; 12 | 13 | import {Well, Call, IWellFunction, IPump, IERC20} from "src/Well.sol"; 14 | import {ConstantProduct2} from "src/functions/ConstantProduct2.sol"; 15 | import {Aquifer} from "src/Aquifer.sol"; 16 | 17 | /** 18 | * @dev Deploys a BEAN:WETH ConstantProduct2 Well. Intended for testing. 19 | */ 20 | contract DeployWell is Script, WellDeployer { 21 | IERC20 constant BEAN = IERC20(0xBEA0000029AD1c77D3d5D23Ba2D8893dB9d1Efab); 22 | IERC20 constant WETH = IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); // WETH9 23 | 24 | function run() external { 25 | uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); 26 | vm.startBroadcast(deployerPrivateKey); 27 | 28 | // Tokens 29 | IERC20[] memory tokens = new IERC20[](2); 30 | tokens[0] = BEAN; 31 | tokens[1] = WETH; 32 | 33 | // Well Function 34 | IWellFunction cp2 = new ConstantProduct2(); 35 | Call memory wellFunction = Call(address(cp2), new bytes(0)); 36 | 37 | // Pump 38 | IPump mockPump = new MockPump(); 39 | Call[] memory pumps = new Call[](1); 40 | pumps[0] = Call(address(mockPump), new bytes(0)); 41 | 42 | address aquifer = address(new Aquifer()); 43 | 44 | address wellImplementation = address(new Well()); 45 | 46 | // Well 47 | Well well = encodeAndBoreWell(aquifer, wellImplementation, tokens, wellFunction, pumps, bytes32(0)); 48 | 49 | console.log("Deployed CP2 at address: ", address(cp2)); 50 | console.log("Deployed Pump at address: ", address(pumps[0].target)); 51 | logger.logWell(well); 52 | 53 | vm.stopBroadcast(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /script/deploy/helpers/Logger.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.20; 4 | 5 | import {console2} from "forge-std/console2.sol"; 6 | import {console} from "forge-std/console.sol"; 7 | 8 | import {Well} from "src/Well.sol"; 9 | 10 | library logger { 11 | function logWell(Well well) public view { 12 | console.log("\nWELL:", address(well)); 13 | console.log("Name \t", well.name()); 14 | console.log("Symbol\t", well.symbol()); 15 | console.log("Aquifer \t", well.aquifer()); 16 | console.log("tokens[0]\t", address(well.tokens()[0])); 17 | console.log("tokens[1]\t", address(well.tokens()[1])); 18 | console.log("Data:"); 19 | console.logBytes(well.wellData()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /script/dex-comparisons.js: -------------------------------------------------------------------------------- 1 | const fs = require('node:fs'); 2 | const readline = require('node:readline'); 3 | 4 | const FUZZ_RUNS = 5000; 5 | 6 | const estimateGasUSD = (gasLimit) => { 7 | const BASE_FEE = 30e-9; // 30 gwei 8 | const PRIO_FEE = 1e-9; // 1 gwei 9 | const ETH_PRICE_USD = 1500; // $1500 10 | return gasLimit * (BASE_FEE + PRIO_FEE) * ETH_PRICE_USD; 11 | } 12 | 13 | async function main() { 14 | const fileStream = fs.createReadStream('.dex-comparisons'); 15 | 16 | const rl = readline.createInterface({ 17 | input: fileStream, 18 | crlfDelay: Infinity, 19 | }); 20 | 21 | let csvContent = 'DEX,PAIR,ACTION,AVERAGE,"EST. COST"\n'; 22 | 23 | for await (const line of rl) { 24 | const uniqueTest = line 25 | .split('IntegrationTestGasComparisons:')[1] 26 | .split('testFuzz_')[1] 27 | .split('_'); 28 | 29 | const dex = uniqueTest[0].toUpperCase(); 30 | const pair = uniqueTest[1].toUpperCase(); 31 | const test = uniqueTest[2].split('(uint256)'); 32 | const testAction = test[0].toUpperCase(); 33 | const testAverage = test[1] 34 | .split(`(runs: ${FUZZ_RUNS}, μ:`)[1] 35 | .split(', ~: ')[0] 36 | .trim(); 37 | 38 | csvContent += 39 | dex + ',' + pair + ',' + testAction + ',' + testAverage + ',' + `$${estimateGasUSD(parseInt(testAverage)).toFixed(2)}` + '\n'; 40 | } 41 | 42 | try { 43 | fs.writeFileSync('./.dex-comparisons.csv', csvContent); 44 | console.log('Data written to file successfully.'); 45 | } catch (err) { 46 | console.error('Failed to write to file: ' + err); 47 | throw err; 48 | } 49 | } 50 | 51 | main() 52 | .then(() => process.exit(0)) 53 | .then(() => process.exit(1)); 54 | -------------------------------------------------------------------------------- /script/helpers/WellDeployer.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.20; 3 | 4 | import {LibContractInfo} from "src/libraries/LibContractInfo.sol"; 5 | import {LibWellConstructor} from "src/libraries/LibWellConstructor.sol"; 6 | import {Well, Call, IERC20} from "src/Well.sol"; 7 | import {Aquifer} from "src/Aquifer.sol"; 8 | import {WellUpgradeable} from "src/WellUpgradeable.sol"; 9 | import {LibWellUpgradeableConstructor} from "src/libraries/LibWellUpgradeableConstructor.sol"; 10 | 11 | abstract contract WellDeployer { 12 | /** 13 | * @notice Encode the Well's immutable data, and deploys the well. 14 | * @param _aquifer The address of the Aquifer which will deploy this Well. 15 | * @param _wellImplementation The address of the Well implementation. 16 | * @param _tokens A list of ERC20 tokens supported by the Well. 17 | * @param _wellFunction A single Call struct representing a call to the Well Function. 18 | * @param _pumps An array of Call structs representings calls to Pumps. 19 | * @param _salt The salt to deploy the Well with (`bytes32(0)` for none). See {LibClone}. 20 | */ 21 | function encodeAndBoreWell( 22 | address _aquifer, 23 | address _wellImplementation, 24 | IERC20[] memory _tokens, 25 | Call memory _wellFunction, 26 | Call[] memory _pumps, 27 | bytes32 _salt 28 | ) internal returns (Well _well) { 29 | (bytes memory immutableData, bytes memory initData) = 30 | LibWellConstructor.encodeWellDeploymentData(_aquifer, _tokens, _wellFunction, _pumps); 31 | _well = Well(Aquifer(_aquifer).boreWell(_wellImplementation, immutableData, initData, _salt)); 32 | } 33 | 34 | /** 35 | * @notice Encode the Well's immutable data, and deploys the well. Modified for upgradeable wells. 36 | * @param _aquifer The address of the Aquifer which will deploy this Well. 37 | * @param _wellImplementation The address of the Well implementation. 38 | * @param _tokens A list of ERC20 tokens supported by the Well. 39 | * @param _wellFunction A single Call struct representing a call to the Well Function. 40 | * @param _pumps An array of Call structs representings calls to Pumps. 41 | * @param _salt The salt to deploy the Well with (`bytes32(0)` for none). See {LibClone}. 42 | */ 43 | function encodeAndBoreWellUpgradeable( 44 | address _aquifer, 45 | address _wellImplementation, 46 | IERC20[] memory _tokens, 47 | Call memory _wellFunction, 48 | Call[] memory _pumps, 49 | bytes32 _salt 50 | ) internal returns (WellUpgradeable _well) { 51 | (bytes memory immutableData, bytes memory initData) = 52 | LibWellUpgradeableConstructor.encodeWellDeploymentData(_aquifer, _tokens, _wellFunction, _pumps); 53 | _well = WellUpgradeable(Aquifer(_aquifer).boreWell(_wellImplementation, immutableData, initData, _salt)); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /script/simulations/stableswap/StableswapCalcRatiosLiqSim.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.20; 3 | 4 | import "forge-std/Script.sol"; 5 | import {console} from "forge-std/console.sol"; 6 | import {Stable2} from "src/functions/Stable2.sol"; 7 | import {Stable2LUT1} from "src/functions/StableLUT/Stable2LUT1.sol"; 8 | 9 | /** 10 | * Stable2 well function simulation and precalculations used 11 | * to produce the token ratios for the lookup table needed for the initial 12 | * `calcReserveAtRatioLiquidity` estimates. 13 | */ 14 | contract StableswapCalcRatiosLiqSim is Script { 15 | function run() external { 16 | Stable2LUT1 stable2LUT1 = new Stable2LUT1(); 17 | Stable2 stable2 = new Stable2(address(stable2LUT1)); 18 | console.log("stable2.getAParameter(): %d", stable2LUT1.getAParameter()); 19 | // initial reserves 20 | uint256 init_reserve_x = 1_000_000e18; 21 | uint256 init_reserve_y = 1_000_000e18; 22 | uint256[] memory reserves = new uint256[](2); 23 | reserves[0] = init_reserve_x; 24 | reserves[1] = init_reserve_y; 25 | uint256 reserve_y = init_reserve_y; 26 | bytes memory data = abi.encode(18, 18); 27 | uint256 price; 28 | 29 | // for n times (1...n) : 30 | // 1) modify reserve x_n-1 by some percentage (this changes the pool liquidity) 31 | // 3) calc price_n using calcRate(...) 32 | 33 | // csv header 34 | console.log("Price (P),Reserve (x),Reserve (y)"); 35 | 36 | // calcReserveAtRatioLiquidity 37 | for (uint256 i; i < 20; i++) { 38 | // update reserves 39 | reserve_y = reserve_y * 88 / 100; 40 | reserves[1] = reserve_y; 41 | // mark price 42 | price = stable2.calcRate(reserves, 0, 1, data); 43 | console.log("%d,%d,%d", price, init_reserve_x, reserve_y); 44 | } 45 | 46 | // reset reserves 47 | reserve_y = init_reserve_y; 48 | 49 | // calcReserveAtRatioLiquidity 50 | for (uint256 i; i < 20; i++) { 51 | // update reserves 52 | reserve_y = reserve_y * 98 / 100; 53 | reserves[1] = reserve_y; 54 | // mark price 55 | price = stable2.calcRate(reserves, 0, 1, data); 56 | console.log("%d,%d,%d", price, init_reserve_x, reserve_y); 57 | } 58 | 59 | // reset reserves 60 | reserve_y = init_reserve_y; 61 | 62 | // calcReserveAtRatioLiquidity 63 | for (uint256 i; i < 20; i++) { 64 | // update reserves 65 | reserve_y = reserve_y * 102 / 100; 66 | reserves[1] = reserve_y; 67 | // mark price 68 | price = stable2.calcRate(reserves, 0, 1, data); 69 | console.log("%d,%d,%d", price, init_reserve_x, reserve_y); 70 | } 71 | 72 | // reset reserves 73 | reserve_y = init_reserve_y; 74 | 75 | // calcReserveAtRatioLiquidity 76 | for (uint256 i; i < 20; i++) { 77 | // update reserves 78 | reserve_y = reserve_y * 112 / 100; 79 | reserves[1] = reserve_y; 80 | // mark price 81 | price = stable2.calcRate(reserves, 0, 1, data); 82 | console.log("%d,%d,%d", price, init_reserve_x, reserve_y); 83 | } 84 | 85 | // Extreme prices 86 | 87 | // extreme low 88 | reserve_y = init_reserve_y * 1 / 28; 89 | reserves[1] = reserve_y; 90 | price = stable2.calcRate(reserves, 0, 1, data); 91 | console.log("%d,%d,%d", price, init_reserve_x, reserve_y); 92 | 93 | // extreme high 94 | reserve_y = init_reserve_y * 2000; 95 | reserves[1] = reserve_y; 96 | price = stable2.calcRate(reserves, 0, 1, data); 97 | console.log("%d,%d,%d", price, init_reserve_x, reserve_y); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/Aquifer.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.20; 4 | 5 | import {ReentrancyGuard} from "oz/security/ReentrancyGuard.sol"; 6 | 7 | import {IAquifer} from "src/interfaces/IAquifer.sol"; 8 | import {IWell} from "src/Well.sol"; 9 | import {LibClone} from "src/libraries/LibClone.sol"; 10 | 11 | /** 12 | * @title Aquifer 13 | * @author Brendan, Silo Chad, Brean 14 | * @notice Aquifer is a permissionless Well registry and factory. 15 | * @dev Aquifer deploys Wells by cloning a pre-deployed Well implementation. 16 | */ 17 | contract Aquifer is IAquifer, ReentrancyGuard { 18 | using LibClone for address; 19 | 20 | // A mapping of Well address to the Well implementation addresses 21 | // Mapping gets set on Well deployment 22 | mapping(address => address) public wellImplementation; 23 | 24 | constructor() ReentrancyGuard() {} 25 | 26 | /** 27 | * @dev 28 | * Use `salt == 0` to deploy a new Well with `create` 29 | * Use `salt > 0` to deploy a new Well with `create2` 30 | */ 31 | function boreWell( 32 | address implementation, 33 | bytes calldata immutableData, 34 | bytes calldata initFunctionCall, 35 | bytes32 salt 36 | ) external nonReentrant returns (address well) { 37 | if (immutableData.length > 0) { 38 | if (salt != bytes32(0)) { 39 | // Encode the salt with the `msg.sender` address to prevent frontrunning attack 40 | salt = keccak256(abi.encode(msg.sender, salt)); 41 | well = implementation.cloneDeterministic(immutableData, salt); 42 | } else { 43 | well = implementation.clone(immutableData); 44 | } 45 | } else { 46 | if (salt != bytes32(0)) { 47 | // Encode the salt with the `msg.sender` address to prevent frontrunning attack 48 | salt = keccak256(abi.encode(msg.sender, salt)); 49 | well = implementation.cloneDeterministic(salt); 50 | } else { 51 | well = implementation.clone(); 52 | } 53 | } 54 | 55 | if (initFunctionCall.length > 0) { 56 | (bool success, bytes memory returnData) = well.call(initFunctionCall); 57 | if (!success) { 58 | // Next 5 lines are based on https://ethereum.stackexchange.com/a/83577 59 | if (returnData.length < 68) revert InitFailed(""); 60 | assembly { 61 | returnData := add(returnData, 0x04) 62 | } 63 | revert InitFailed(abi.decode(returnData, (string))); 64 | } 65 | } 66 | 67 | if (!IWell(well).isInitialized()) { 68 | revert WellNotInitialized(); 69 | } 70 | 71 | // The Aquifer address MUST be set, either (a) via immutable data during cloning, 72 | // or (b) as a storage variable during an init function call. In either case, 73 | // the address MUST match the address of the Aquifer that performed deployment. 74 | if (IWell(well).aquifer() != address(this)) { 75 | revert InvalidConfig(); 76 | } 77 | 78 | // Save implementation 79 | wellImplementation[well] = implementation; 80 | 81 | emit BoreWell( 82 | well, 83 | implementation, 84 | IWell(well).tokens(), 85 | IWell(well).wellFunction(), 86 | IWell(well).pumps(), 87 | IWell(well).wellData() 88 | ); 89 | } 90 | 91 | function predictWellAddress( 92 | address implementation, 93 | bytes calldata immutableData, 94 | bytes32 salt 95 | ) external view returns (address well) { 96 | // Aquifer doesn't support using a salt of 0 to deploy a Well at a deterministic address. 97 | if (salt == bytes32(0)) { 98 | revert InvalidSalt(); 99 | } 100 | salt = keccak256(abi.encode(msg.sender, salt)); 101 | if (immutableData.length > 0) { 102 | well = implementation.predictDeterministicAddress(immutableData, salt, address(this)); 103 | } else { 104 | well = implementation.predictDeterministicAddress(salt, address(this)); 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/functions/ConstantProduct.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.20; 4 | 5 | import {IBeanstalkWellFunction} from "src/interfaces/IBeanstalkWellFunction.sol"; 6 | import {ProportionalLPToken} from "src/functions/ProportionalLPToken.sol"; 7 | import {LibMath} from "src/libraries/LibMath.sol"; 8 | 9 | /** 10 | * @title ConstantProduct 11 | * @author Brendan 12 | * @notice Constant product pricing function for Wells with N tokens. 13 | * @dev Constant Product Wells use the formula: 14 | * `π(b_i) = (s / n)^n` 15 | * 16 | * Where: 17 | * `s` is the supply of LP tokens 18 | * `b_i` is the reserve at index `i` 19 | * `n` is the number of tokens in the Well 20 | * 21 | * Note: Using too many tokens in a Constant Product Well may result in overflow. 22 | */ 23 | contract ConstantProduct is ProportionalLPToken, IBeanstalkWellFunction { 24 | using LibMath for uint256; 25 | 26 | uint256 constant CALC_RATE_PRECISION = 1e18; 27 | 28 | /// @dev `s = π(b_i)^(1/n) * n` 29 | function calcLpTokenSupply( 30 | uint256[] calldata reserves, 31 | bytes calldata 32 | ) external pure override returns (uint256 lpTokenSupply) { 33 | lpTokenSupply = _prodX(reserves).nthRoot(reserves.length) * reserves.length; 34 | } 35 | 36 | /// @dev `b_j = (s / n)^n / π_{i!=j}(b_i)` 37 | function calcReserve( 38 | uint256[] calldata reserves, 39 | uint256 j, 40 | uint256 lpTokenSupply, 41 | bytes calldata 42 | ) external pure override returns (uint256 reserve) { 43 | uint256 n = reserves.length; 44 | reserve = (lpTokenSupply / n) ** n; 45 | for (uint256 i; i < n; ++i) { 46 | if (i != j) reserve = reserve / reserves[i]; 47 | } 48 | } 49 | 50 | function name() external pure override returns (string memory) { 51 | return "Constant Product"; 52 | } 53 | 54 | function symbol() external pure override returns (string memory) { 55 | return "CP"; 56 | } 57 | 58 | /// @dev calculate the mathematical product of an array of uint256[] 59 | function _prodX(uint256[] memory xs) private pure returns (uint256 pX) { 60 | pX = xs[0]; 61 | uint256 length = xs.length; 62 | for (uint256 i = 1; i < length; ++i) { 63 | pX = pX * xs[i]; 64 | } 65 | } 66 | 67 | /// @dev `b_j = (π(b_i) * r_j / (Σ_{i != j}(r_i)/(n-1)))^(1/n)` 68 | function calcReserveAtRatioSwap( 69 | uint256[] calldata reserves, 70 | uint256 j, 71 | uint256[] calldata ratios, 72 | bytes calldata 73 | ) external pure override returns (uint256 reserve) { 74 | uint256 sumRatio = 0; 75 | for (uint256 i; i < reserves.length; ++i) { 76 | if (i != j) sumRatio += ratios[i]; 77 | } 78 | sumRatio /= reserves.length - 1; 79 | reserve = _prodX(reserves) * ratios[j] / sumRatio; 80 | reserve = reserve.nthRoot(reserves.length); 81 | } 82 | 83 | /// @dev `b_j = Σ_{i != j}(b_i * r_j / r_i) / (n-1)` 84 | function calcReserveAtRatioLiquidity( 85 | uint256[] calldata reserves, 86 | uint256 j, 87 | uint256[] calldata ratios, 88 | bytes calldata 89 | ) external pure override returns (uint256 reserve) { 90 | for (uint256 i; i < reserves.length; ++i) { 91 | if (i != j) { 92 | reserve += ratios[j] * reserves[i] / ratios[i]; 93 | } 94 | } 95 | reserve /= reserves.length - 1; 96 | } 97 | 98 | function calcRate( 99 | uint256[] calldata reserves, 100 | uint256 i, 101 | uint256 j, 102 | bytes calldata 103 | ) external pure returns (uint256 rate) { 104 | return reserves[i] * CALC_RATE_PRECISION / reserves[j]; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/functions/ProportionalLPToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.20; 4 | 5 | import {IWellFunction} from "src/interfaces/IWellFunction.sol"; 6 | import {Math} from "oz/utils/math/Math.sol"; 7 | 8 | /** 9 | * @title ProportionalLPToken 10 | * @notice Defines a proportional relationship between the supply of LP tokens 11 | * and the amount of each underlying token for an N-token Well. 12 | * @dev When removing `s` LP tokens with a Well with `S` LP token supply, the user 13 | * recieves `s * b_i / S` of each underlying token. 14 | */ 15 | abstract contract ProportionalLPToken is IWellFunction { 16 | using Math for uint256; 17 | 18 | function calcLPTokenUnderlying( 19 | uint256 lpTokenAmount, 20 | uint256[] calldata reserves, 21 | uint256 lpTokenSupply, 22 | bytes calldata 23 | ) external pure returns (uint256[] memory underlyingAmounts) { 24 | underlyingAmounts = new uint256[](reserves.length); 25 | for (uint256 i; i < reserves.length; ++i) { 26 | underlyingAmounts[i] = lpTokenAmount.mulDiv(reserves[i], lpTokenSupply); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/functions/ProportionalLPToken2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.20; 4 | 5 | import {IWellFunction} from "src/interfaces/IWellFunction.sol"; 6 | import {Math} from "oz/utils/math/Math.sol"; 7 | 8 | /** 9 | * @title ProportionalLPToken2 10 | * @notice Defines a proportional relationship between the supply of LP tokens 11 | * and the amount of each underlying token for a two-token Well. 12 | * @dev When removing `s` LP tokens with a Well with `S` LP token supply, the user 13 | * recieves `s * b_i / S` of each underlying token. 14 | */ 15 | abstract contract ProportionalLPToken2 is IWellFunction { 16 | using Math for uint256; 17 | 18 | function calcLPTokenUnderlying( 19 | uint256 lpTokenAmount, 20 | uint256[] calldata reserves, 21 | uint256 lpTokenSupply, 22 | bytes calldata 23 | ) external pure returns (uint256[] memory underlyingAmounts) { 24 | underlyingAmounts = new uint256[](2); 25 | underlyingAmounts[0] = lpTokenAmount.mulDiv(reserves[0], lpTokenSupply); 26 | underlyingAmounts[1] = lpTokenAmount.mulDiv(reserves[1], lpTokenSupply); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/interfaces/IAquifer.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.20; 4 | 5 | import {IERC20, SafeERC20} from "oz/token/ERC20/utils/SafeERC20.sol"; 6 | import {IWell, Call} from "src/interfaces/IWell.sol"; 7 | 8 | /** 9 | * @title IAquifer 10 | * @notice Interface for the Aquifer, a permissionless Well deployer and registry. 11 | */ 12 | interface IAquifer { 13 | /** 14 | * @notice Thrown when the {init} function call on the Well reverts. 15 | */ 16 | error InitFailed(string reason); 17 | 18 | /** 19 | * @notice Thrown when the user attempts to bore a Well with invalid configuration. 20 | */ 21 | error InvalidConfig(); 22 | 23 | /** 24 | * @notice Thrown a Well is bored, but not initialized. 25 | */ 26 | error WellNotInitialized(); 27 | 28 | /** 29 | * @notice Thrown when the user attempts to predict a Well's deterministic address with a salt of 0. 30 | */ 31 | error InvalidSalt(); 32 | 33 | /** 34 | * @notice Emitted when a Well is deployed. 35 | * @param well The address of the new Well 36 | * @param implementation The Well implementation address 37 | * @param tokens The tokens in the Well 38 | * @param wellFunction The Well function 39 | * @param pumps The pumps to bore in the Well 40 | * @param wellData The Well data to implement into the Well 41 | */ 42 | event BoreWell( 43 | address well, address implementation, IERC20[] tokens, Call wellFunction, Call[] pumps, bytes wellData 44 | ); 45 | 46 | /** 47 | * @notice Deploys a Well. 48 | * @param implementation The Well implementation to clone. 49 | * @param immutableData The data to append to the bytecode of the contract. 50 | * @param initFunctionCall The function call to initialize the Well. Set to empty bytes for no call. 51 | * @param salt The salt to deploy the Well with (`bytes32(0)` for none). See {LibClone}. 52 | * @return wellAddress The address of the new Well 53 | */ 54 | function boreWell( 55 | address implementation, 56 | bytes calldata immutableData, 57 | bytes calldata initFunctionCall, 58 | bytes32 salt 59 | ) external returns (address wellAddress); 60 | 61 | /** 62 | * @notice Returns the implementation that a given Well was deployed with. 63 | * @param well The Well to get the implementation of 64 | * @return implementation The address of the implementation of a Well. 65 | * @dev Always verify that a Well was deployed by a trusted Aquifer using a trusted implementation before using. 66 | * If `wellImplementation == address(0)`, then the Aquifer did not deploy the Well. 67 | */ 68 | function wellImplementation(address well) external view returns (address implementation); 69 | } 70 | -------------------------------------------------------------------------------- /src/interfaces/IBeanstalkWellFunction.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.20; 4 | 5 | import {IMultiFlowPumpWellFunction} from "src/interfaces/IMultiFlowPumpWellFunction.sol"; 6 | 7 | /** 8 | * @title IBeanstalkWellFunction 9 | * @notice Defines all necessary functions for Beanstalk to support a Well Function in addition to functions defined in the primary interface. 10 | * It extends `IMultiFlowPumpWellFunction` as Beanstalk requires Wells to use MultiFlowPump in order to have access to manipulation resistant oracles. 11 | * Beanstalk requires 2 functions to solve for a given reserve value such that the average price between 12 | * the given reserve and all other reserves equals the average of the input ratios. 13 | * - `calcReserveAtRatioSwap` assumes the target ratios are reached through executing a swap. Note: `calcReserveAtRatioSwap` is included in {IMultiFlowPumpWellFunction}. 14 | * - `calcReserveAtRatioLiquidity` assumes the target ratios are reached through adding/removing liquidity. 15 | */ 16 | interface IBeanstalkWellFunction is IMultiFlowPumpWellFunction { 17 | /** 18 | * @notice Calculates the `j` reserve such that `π_{i | i != j} (d reserves_j / d reserves_i) = π_{i | i != j}(ratios_j / ratios_i)`. 19 | * assumes that reserve_j is being added or removed in exchange for LP Tokens. 20 | * @dev used by Beanstalk to calculate the max deltaB that can be converted in/out of a Well. 21 | * @param reserves The reserves of the Well 22 | * @param j The index of the reserve to solve for 23 | * @param ratios The ratios of reserves to solve for 24 | * @param data Well function data provided on every call 25 | * @return reserve The resulting reserve at the jth index 26 | */ 27 | function calcReserveAtRatioLiquidity( 28 | uint256[] calldata reserves, 29 | uint256 j, 30 | uint256[] calldata ratios, 31 | bytes calldata data 32 | ) external view returns (uint256 reserve); 33 | } 34 | -------------------------------------------------------------------------------- /src/interfaces/ILookupTable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.17; 4 | 5 | /** 6 | * @title PriceReserveMapping 7 | * @author DeadmanWalking 8 | * @notice In order to reasonably use `calcReserveAtRatioSwap` and `calcReserveAtRatioLiquidity` on chain, 9 | * a lookup table contract is used to decrease the amount of iterations needed to converge into an answer. 10 | */ 11 | interface ILookupTable { 12 | /** 13 | * @notice the lookup table returns a series of data, given a price point: 14 | * @param highPrice the closest price to the targetPrice, where targetPrice < highPrice. 15 | * @param highPriceI reserve i such that `calcRate(reserve, i, j, data)` == highPrice. 16 | * @param highPriceJ reserve j such that `calcRate(reserve, i, j, data)` == highPrice. 17 | * @param lowPrice the closest price to the targetPrice, where targetPrice > lowPrice. 18 | * @param lowPriceI reserve i such that `calcRate(reserve, i, j, data)` == lowPrice. 19 | * @param lowPriceJ reserve j such that `calcRate(reserve, i, j, data)` == lowPrice. 20 | * @param precision the initial reserve values. Assumes the inital reserve i == reserve j 21 | */ 22 | struct PriceData { 23 | uint256 highPrice; 24 | uint256 highPriceI; 25 | uint256 highPriceJ; 26 | uint256 lowPrice; 27 | uint256 lowPriceI; 28 | uint256 lowPriceJ; 29 | uint256 precision; 30 | } 31 | 32 | function getRatiosFromPriceLiquidity(uint256) external view returns (PriceData memory); 33 | function getRatiosFromPriceSwap(uint256) external view returns (PriceData memory); 34 | function getAParameter() external view returns (uint256); 35 | } 36 | -------------------------------------------------------------------------------- /src/interfaces/IMultiFlowPumpWellFunction.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.20; 4 | 5 | import {IWellFunction} from "src/interfaces/IWellFunction.sol"; 6 | 7 | /** 8 | * @title IMultiFlowPumpWellFunction 9 | * @dev A Well Function must implement IMultiFlowPumpWellFunction to be supported by 10 | * the Multi Flow Pump. 11 | */ 12 | interface IMultiFlowPumpWellFunction is IWellFunction { 13 | /** 14 | * @notice Calculates the `j` reserve such that `π_{i | i != j} (d reserves_j / d reserves_i) = π_{i | i != j}(ratios_j / ratios_i)`. 15 | * assumes that reserve_j is being swapped for other reserves in the Well. 16 | * @dev used by Beanstalk to calculate the deltaB every Season 17 | * @param reserves The reserves of the Well 18 | * @param j The index of the reserve to solve for 19 | * @param ratios The ratios of reserves to solve for 20 | * @param data Well function data provided on every call 21 | * @return reserve The resulting reserve at the jth index 22 | */ 23 | function calcReserveAtRatioSwap( 24 | uint256[] calldata reserves, 25 | uint256 j, 26 | uint256[] calldata ratios, 27 | bytes calldata data 28 | ) external view returns (uint256 reserve); 29 | 30 | /** 31 | * @notice Calculates the rate at which j can be exchanged for i. 32 | * @param reserves The reserves of the Well 33 | * @param i The index of the token for which the output is being calculated 34 | * @param j The index of the token for which 1 token is being exchanged 35 | * @param data Well function data provided on every call 36 | * @return rate The rate at which j can be exchanged for i 37 | * @dev should return with 36 decimal precision 38 | */ 39 | function calcRate( 40 | uint256[] calldata reserves, 41 | uint256 i, 42 | uint256 j, 43 | bytes calldata data 44 | ) external view returns (uint256 rate); 45 | } 46 | -------------------------------------------------------------------------------- /src/interfaces/IWellErrors.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.20; 4 | 5 | import {IERC20} from "oz/token/ERC20/IERC20.sol"; 6 | 7 | /** 8 | * @title IWellErrors defines all Well errors. 9 | * @dev The errors are separated into a different interface as not all Well 10 | * implementations may share the same errors. 11 | */ 12 | interface IWellErrors { 13 | /** 14 | * @notice Thrown when an operation would deliver fewer tokens than `minAmountOut`. 15 | */ 16 | error SlippageOut(uint256 amountOut, uint256 minAmountOut); 17 | 18 | /** 19 | * @notice Thrown when an operation would require more tokens than `maxAmountIn`. 20 | */ 21 | error SlippageIn(uint256 amountIn, uint256 maxAmountIn); 22 | 23 | /** 24 | * @notice Thrown if one or more tokens used in the operation are not supported by the Well. 25 | */ 26 | error InvalidTokens(); 27 | 28 | /** 29 | * @notice Thrown if this operation would cause an incorrect change in Well reserves. 30 | */ 31 | error InvalidReserves(); 32 | 33 | /** 34 | * @notice Thrown when a Well is bored with duplicate tokens. 35 | */ 36 | error DuplicateTokens(IERC20 token); 37 | 38 | /** 39 | * @notice Thrown if an operation is executed after the provided `deadline` has passed. 40 | */ 41 | error Expired(); 42 | } 43 | -------------------------------------------------------------------------------- /src/interfaces/IWellFunction.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.20; 4 | 5 | /** 6 | * @title IWellFunction 7 | * @notice Defines a relationship between token reserves and LP token supply. 8 | * @dev Well Functions can contain arbitrary logic, but should be deterministic 9 | * if expected to be used alongside a Pump. When interacing with a Well or 10 | * Well Function, always verify that the Well Function is valid. 11 | */ 12 | interface IWellFunction { 13 | /** 14 | * @notice Thrown if the user inputs a `j` value is out of bounds. 15 | */ 16 | error InvalidJArgument(); 17 | 18 | /** 19 | * @notice Calculates the `j`th reserve given a list of `reserves` and `lpTokenSupply`. 20 | * @param reserves A list of token reserves. The jth reserve will be ignored, but a placeholder must be provided. 21 | * @param j The index of the reserve to solve for 22 | * @param lpTokenSupply The supply of LP tokens 23 | * @param data Extra Well function data provided on every call 24 | * @return reserve The resulting reserve at the jth index 25 | * @dev Should round up to ensure that Well reserves are marginally higher to enforce calcLpTokenSupply(...) >= totalSupply() 26 | */ 27 | function calcReserve( 28 | uint256[] memory reserves, 29 | uint256 j, 30 | uint256 lpTokenSupply, 31 | bytes calldata data 32 | ) external view returns (uint256 reserve); 33 | 34 | /** 35 | * @notice Gets the LP token supply given a list of reserves. 36 | * @param reserves A list of token reserves 37 | * @param data Extra Well function data provided on every call 38 | * @return lpTokenSupply The resulting supply of LP tokens 39 | * @dev Should round down to ensure so that the Well Token supply is marignally lower to enforce calcLpTokenSupply(...) >= totalSupply() 40 | */ 41 | function calcLpTokenSupply( 42 | uint256[] memory reserves, 43 | bytes calldata data 44 | ) external view returns (uint256 lpTokenSupply); 45 | 46 | /** 47 | * @notice Calculates the amount of each reserve token underlying a given amount of LP tokens. 48 | * @param lpTokenAmount An amount of LP tokens 49 | * @param reserves A list of token reserves 50 | * @param lpTokenSupply The current supply of LP tokens 51 | * @param data Extra Well function data provided on every call 52 | * @return underlyingAmounts The amount of each reserve token that underlies the LP tokens 53 | * @dev The constraint totalSupply() <= calcLPTokenSupply(...) must be held in the case where 54 | * `lpTokenAmount` LP tokens are burned in exchanged for `underlyingAmounts`. If the constraint 55 | * does not hold, then the Well Function is invalid. 56 | */ 57 | function calcLPTokenUnderlying( 58 | uint256 lpTokenAmount, 59 | uint256[] memory reserves, 60 | uint256 lpTokenSupply, 61 | bytes calldata data 62 | ) external view returns (uint256[] memory underlyingAmounts); 63 | 64 | /** 65 | * @notice Returns the name of the Well function. 66 | */ 67 | function name() external view returns (string memory); 68 | 69 | /** 70 | * @notice Returns the symbol of the Well function. 71 | */ 72 | function symbol() external view returns (string memory); 73 | } 74 | -------------------------------------------------------------------------------- /src/interfaces/pumps/ICumulativePump.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.20; 4 | 5 | /** 6 | * @title ICumulativePump 7 | * @notice Provides an interface for Pumps which calculate time-weighted average 8 | * reserves through the use of a cumulative reserve. 9 | */ 10 | interface ICumulativePump { 11 | /** 12 | * @notice Reads the current cumulative reserves from the Pump 13 | * @param well The address of the Well 14 | * @param data data specific to the Well 15 | * @return cumulativeReserves The cumulative reserves from the Pump 16 | */ 17 | function readCumulativeReserves( 18 | address well, 19 | bytes memory data 20 | ) external view returns (bytes memory cumulativeReserves); 21 | 22 | /** 23 | * @notice Reads the current cumulative reserves from the Pump 24 | * @param well The address of the Well 25 | * @param startCumulativeReserves The cumulative reserves to start the TWA from 26 | * @param startTimestamp The timestamp to start the TWA from 27 | * @param data data specific to the Well 28 | * @return twaReserves The time weighted average reserves from start timestamp to now 29 | * @return cumulativeReserves The current cumulative reserves from the Pump at the current timestamp 30 | */ 31 | function readTwaReserves( 32 | address well, 33 | bytes calldata startCumulativeReserves, 34 | uint256 startTimestamp, 35 | bytes memory data 36 | ) external view returns (uint256[] memory twaReserves, bytes memory cumulativeReserves); 37 | } 38 | -------------------------------------------------------------------------------- /src/interfaces/pumps/IInstantaneousPump.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.20; 4 | 5 | /** 6 | * @title Instantaneous Pumps provide an Oracle for instantaneous reserves. 7 | */ 8 | interface IInstantaneousPump { 9 | /** 10 | * @notice Reads instantaneous reserves from the Pump 11 | * @param well The address of the Well 12 | * @return reserves The instantaneous balanecs tracked by the Pump 13 | */ 14 | function readInstantaneousReserves( 15 | address well, 16 | bytes memory data 17 | ) external view returns (uint256[] memory reserves); 18 | } 19 | -------------------------------------------------------------------------------- /src/interfaces/pumps/IMultiFlowPumpErrors.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.20; 4 | 5 | /** 6 | * @title IMultiFlowPumpErrors defines the errors for the MultiFlowPump. 7 | * @dev The errors are separated into a different interface as not all Pump 8 | * implementations may share the same errors. 9 | */ 10 | interface IMultiFlowPumpErrors { 11 | error NotInitialized(); 12 | 13 | error NoTimePassed(); 14 | 15 | error TooManyTokens(); 16 | } 17 | -------------------------------------------------------------------------------- /src/interfaces/pumps/IPump.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.20; 4 | 5 | /** 6 | * @title IPump defines the interface for a Pump. 7 | * 8 | * @dev Pumps are on-chain oracles that are updated upon each interaction with a {IWell}. 9 | * When reading a Pump, always verify the Pump's functionality. 10 | */ 11 | interface IPump { 12 | /** 13 | * @notice Updates the Pump with the given reserves. 14 | * @param reserves The previous reserves of the tokens in the Well. 15 | * @param data data specific to the Well 16 | * @dev Pumps are updated every time a user swaps, adds liquidity, or 17 | * removes liquidity from a Well. 18 | */ 19 | function update(uint256[] calldata reserves, bytes calldata data) external; 20 | } 21 | -------------------------------------------------------------------------------- /src/libraries/LibBytes16.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.20; 4 | 5 | /** 6 | * @title LibBytes16 7 | * @author Brendan 8 | * @notice Contains byte operations used during storage reads & writes for Pumps. 9 | * 10 | * {LibBytes16} tightly packs an array of `bytes16` values into `n / 2` storage 11 | * slots, where `n` is number of items to pack. 12 | */ 13 | library LibBytes16 { 14 | /** 15 | * @dev Store packed bytes16 `reserves` starting at storage position `slot`. 16 | */ 17 | function storeBytes16(bytes32 slot, bytes16[] memory reserves) internal { 18 | // Shortcut: two reserves can be packed into one slot without a loop 19 | if (reserves.length == 2) { 20 | assembly { 21 | sstore(slot, add(mload(add(reserves, 32)), shr(128, mload(add(reserves, 64))))) 22 | } 23 | } else { 24 | uint256 maxI = reserves.length / 2; // number of fully-packed slots 25 | uint256 iByte; // byte offset of the current reserve 26 | for (uint256 i; i < maxI; ++i) { 27 | iByte = i * 64; 28 | assembly { 29 | sstore( 30 | add(slot, i), 31 | add(mload(add(reserves, add(iByte, 32))), shr(128, mload(add(reserves, add(iByte, 64))))) 32 | ) 33 | } 34 | } 35 | // If there is an odd number of reserves, create a slot with the last reserve 36 | // Since `i < maxI` above, the next byte offset `maxI * 64` 37 | // Equivalent to "reserves.length % 2 == 1", but cheaper. 38 | if (reserves.length & 1 == 1) { 39 | iByte = maxI * 64; 40 | assembly { 41 | sstore( 42 | add(slot, maxI), 43 | add(mload(add(reserves, add(iByte, 32))), shr(128, shl(128, sload(add(slot, maxI))))) 44 | ) 45 | } 46 | } 47 | } 48 | } 49 | 50 | /** 51 | * @dev Read `n` packed uint128 reserves at storage position `slot`. 52 | */ 53 | function readBytes16(bytes32 slot, uint256 n) internal view returns (bytes16[] memory reserves) { 54 | // Initialize array with length `n`, fill it in via assembly 55 | reserves = new bytes16[](n); 56 | 57 | // Shortcut: two reserves can be quickly unpacked from one slot 58 | if (n == 2) { 59 | assembly { 60 | mstore(add(reserves, 32), sload(slot)) 61 | mstore(add(reserves, 64), shl(128, sload(slot))) 62 | } 63 | return reserves; 64 | } 65 | 66 | uint256 iByte; 67 | for (uint256 i = 1; i <= n; ++i) { 68 | // `iByte` is the byte position for the current slot: 69 | // i 1 2 3 4 5 6 70 | // iByte 0 0 1 1 2 2 71 | iByte = (i - 1) / 2; 72 | // Equivalent to "i % 2 == 1" but cheaper. 73 | if (i & 1 == 1) { 74 | assembly { 75 | mstore( 76 | // store at index i * 32; i = 0 is skipped by loop 77 | add(reserves, mul(i, 32)), 78 | sload(add(slot, iByte)) 79 | ) 80 | } 81 | } else { 82 | assembly { 83 | mstore(add(reserves, mul(i, 32)), shl(128, sload(add(slot, iByte)))) 84 | } 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/libraries/LibContractInfo.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.20; 4 | 5 | /** 6 | * @title LibContractInfo 7 | * @notice Contains logic to call functions that return information about a given contract. 8 | */ 9 | library LibContractInfo { 10 | /** 11 | * @notice gets the symbol of a given contract 12 | * @param _contract The contract to get the symbol of 13 | * @return symbol The symbol of the contract 14 | * @dev if the contract does not have a symbol function, the first 4 bytes of the address are returned 15 | */ 16 | function getSymbol(address _contract) internal view returns (string memory symbol) { 17 | (bool success, bytes memory data) = _contract.staticcall(abi.encodeWithSignature("symbol()")); 18 | symbol = new string(4); 19 | if (success) { 20 | symbol = abi.decode(data, (string)); 21 | } else { 22 | assembly { 23 | mstore(add(symbol, 0x20), shl(224, shr(128, _contract))) 24 | } 25 | } 26 | } 27 | 28 | /** 29 | * @notice gets the name of a given contract 30 | * @param _contract The contract to get the name of 31 | * @return name The name of the contract 32 | * @dev if the contract does not have a name function, the first 8 bytes of the address are returned 33 | */ 34 | function getName(address _contract) internal view returns (string memory name) { 35 | (bool success, bytes memory data) = _contract.staticcall(abi.encodeWithSignature("name()")); 36 | name = new string(8); 37 | if (success) { 38 | name = abi.decode(data, (string)); 39 | } else { 40 | assembly { 41 | mstore(add(name, 0x20), shl(224, shr(128, _contract))) 42 | } 43 | } 44 | } 45 | 46 | /** 47 | * @notice gets the decimals of a given contract 48 | * @param _contract The contract to get the decimals of 49 | * @return decimals The decimals of the contract 50 | * @dev if the contract does not have a decimals function, 18 is returned 51 | */ 52 | function getDecimals(address _contract) internal view returns (uint8 decimals) { 53 | (bool success, bytes memory data) = _contract.staticcall(abi.encodeWithSignature("decimals()")); 54 | decimals = success ? abi.decode(data, (uint8)) : 18; // default to 18 decimals 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/libraries/LibWellConstructor.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // forgefmt: disable-start 3 | 4 | pragma solidity ^0.8.20; 5 | 6 | import {LibContractInfo} from "src/libraries/LibContractInfo.sol"; 7 | import {Call, IERC20} from "src/Well.sol"; 8 | 9 | library LibWellConstructor { 10 | /** 11 | * @notice Encode the Well's immutable data and init data. 12 | */ 13 | function encodeWellDeploymentData( 14 | address _aquifer, 15 | IERC20[] memory _tokens, 16 | Call memory _wellFunction, 17 | Call[] memory _pumps 18 | ) internal view returns (bytes memory immutableData, bytes memory initData) { 19 | immutableData = encodeWellImmutableData(_aquifer, _tokens, _wellFunction, _pumps); 20 | initData = encodeWellInitFunctionCall(_tokens, _wellFunction); 21 | } 22 | 23 | /** 24 | * @notice Encode the Well's immutable data. 25 | * @param _aquifer The address of the Aquifer which will deploy this Well. 26 | * @param _tokens A list of ERC20 tokens supported by the Well. 27 | * @param _wellFunction A single Call struct representing a call to the Well Function. 28 | * @param _pumps An array of Call structs representings calls to Pumps. 29 | * @dev `immutableData` is tightly packed, however since `_tokens` itself is 30 | * an array, each address in the array will be padded up to 32 bytes. 31 | * 32 | * Arbitrary-length bytes are applied to the end of the encoded bytes array 33 | * for easy reading of statically-sized data. 34 | * 35 | */ 36 | function encodeWellImmutableData( 37 | address _aquifer, 38 | IERC20[] memory _tokens, 39 | Call memory _wellFunction, 40 | Call[] memory _pumps 41 | ) internal pure returns (bytes memory immutableData) { 42 | 43 | immutableData = abi.encodePacked( 44 | _aquifer, // aquifer address 45 | _tokens.length, // number of tokens 46 | _wellFunction.target, // well function address 47 | _wellFunction.data.length, // well function data length 48 | _pumps.length, // number of pumps 49 | _tokens, // tokens array 50 | _wellFunction.data // well function data (bytes) 51 | ); 52 | for (uint256 i; i < _pumps.length; ++i) { 53 | immutableData = abi.encodePacked( 54 | immutableData, // previously packed pumps 55 | _pumps[i].target, // pump address 56 | _pumps[i].data.length, // pump data length 57 | _pumps[i].data // pump data (bytes) 58 | ); 59 | } 60 | } 61 | 62 | /** 63 | * @notice Encode a call to the {Well.init} function to set a standardized ERC20 name and symbol. 64 | */ 65 | function encodeWellInitFunctionCall( 66 | IERC20[] memory _tokens, 67 | Call memory _wellFunction 68 | ) public view returns (bytes memory initFunctionCall) { 69 | string memory name = LibContractInfo.getSymbol(address(_tokens[0])); 70 | string memory symbol = name; 71 | for (uint256 i = 1; i < _tokens.length; ++i) { 72 | name = string.concat(name, ":", LibContractInfo.getSymbol(address(_tokens[i]))); 73 | symbol = string.concat(symbol, LibContractInfo.getSymbol(address(_tokens[i]))); 74 | } 75 | name = string.concat(name, " ", LibContractInfo.getName(_wellFunction.target), " Well"); 76 | symbol = string.concat(symbol, LibContractInfo.getSymbol(_wellFunction.target), "w"); 77 | 78 | // See {Well.init}. 79 | initFunctionCall = abi.encodeWithSignature("init(string,string)", name, symbol); 80 | } 81 | 82 | /** 83 | * @notice Encode a Call struct representing an arbitrary call to `target` with additional data `data`. 84 | */ 85 | function encodeCall(address target, bytes memory data) public pure returns (Call memory) { 86 | return Call(target, data); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/libraries/LibWellUpgradeableConstructor.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // forgefmt: disable-start 3 | 4 | pragma solidity ^0.8.20; 5 | 6 | import {LibContractInfo} from "src/libraries/LibContractInfo.sol"; 7 | import {Call, IERC20} from "src/Well.sol"; 8 | import {WellUpgradeable} from "src/WellUpgradeable.sol"; 9 | 10 | library LibWellUpgradeableConstructor { 11 | 12 | /** 13 | * @notice Encode the Well's immutable data. 14 | */ 15 | function encodeWellDeploymentData( 16 | address _aquifer, 17 | IERC20[] memory _tokens, 18 | Call memory _wellFunction, 19 | Call[] memory _pumps 20 | ) internal pure returns (bytes memory immutableData, bytes memory initData) { 21 | immutableData = encodeWellImmutableData(_aquifer, _tokens, _wellFunction, _pumps); 22 | initData = abi.encodeWithSelector(WellUpgradeable.initNoWellToken.selector); 23 | } 24 | 25 | /** 26 | * @notice Encode the Well's immutable data. 27 | * @param _aquifer The address of the Aquifer which will deploy this Well. 28 | * @param _tokens A list of ERC20 tokens supported by the Well. 29 | * @param _wellFunction A single Call struct representing a call to the Well Function. 30 | * @param _pumps An array of Call structs representings calls to Pumps. 31 | * @dev `immutableData` is tightly packed, however since `_tokens` itself is 32 | * an array, each address in the array will be padded up to 32 bytes. 33 | * 34 | * Arbitrary-length bytes are applied to the end of the encoded bytes array 35 | * for easy reading of statically-sized data. 36 | * 37 | */ 38 | function encodeWellImmutableData( 39 | address _aquifer, 40 | IERC20[] memory _tokens, 41 | Call memory _wellFunction, 42 | Call[] memory _pumps 43 | ) internal pure returns (bytes memory immutableData) { 44 | 45 | immutableData = abi.encodePacked( 46 | _aquifer, // aquifer address 47 | _tokens.length, // number of tokens 48 | _wellFunction.target, // well function address 49 | _wellFunction.data.length, // well function data length 50 | _pumps.length, // number of pumps 51 | _tokens, // tokens array 52 | _wellFunction.data // well function data (bytes) 53 | ); 54 | for (uint256 i; i < _pumps.length; ++i) { 55 | immutableData = abi.encodePacked( 56 | immutableData, // previously packed pumps 57 | _pumps[i].target, // pump address 58 | _pumps[i].data.length, // pump data length 59 | _pumps[i].data // pump data (bytes) 60 | ); 61 | } 62 | } 63 | 64 | function encodeWellInitFunctionCall( 65 | IERC20[] memory _tokens, 66 | Call memory _wellFunction 67 | ) public view returns (bytes memory initFunctionCall) { 68 | string memory name = LibContractInfo.getSymbol(address(_tokens[0])); 69 | string memory symbol = name; 70 | for (uint256 i = 1; i < _tokens.length; ++i) { 71 | name = string.concat(name, ":", LibContractInfo.getSymbol(address(_tokens[i]))); 72 | symbol = string.concat(symbol, LibContractInfo.getSymbol(address(_tokens[i]))); 73 | } 74 | name = string.concat(name, " ", LibContractInfo.getName(_wellFunction.target), " Upgradeable Well"); 75 | symbol = string.concat(symbol, LibContractInfo.getSymbol(_wellFunction.target), "uw"); 76 | 77 | // See {Well.init}. 78 | initFunctionCall = abi.encodeWithSelector(WellUpgradeable.init.selector, name, symbol); 79 | } 80 | 81 | /** 82 | * @notice Encode a Call struct representing an arbitrary call to `target` with additional data `data`. 83 | */ 84 | function encodeCall(address target, bytes memory data) public pure returns (Call memory) { 85 | return Call(target, data); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/utils/Clone.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD 2 | pragma solidity ^0.8.20; 3 | 4 | /// @title Clone 5 | /// @author zefram.eth, Saw-mon & Natalie 6 | /// @notice Provides helper functions for reading immutable args from calldata 7 | contract Clone { 8 | 9 | uint256 internal constant ONE_WORD = 0x20; 10 | 11 | /// @notice Reads an immutable arg with type address 12 | /// @param argOffset The offset of the arg in the packed data 13 | /// @return arg The arg value 14 | function _getArgAddress(uint256 argOffset) 15 | internal 16 | pure 17 | returns (address arg) 18 | { 19 | uint256 offset = _getImmutableArgsOffset(); 20 | // solhint-disable-next-line no-inline-assembly 21 | assembly { 22 | arg := shr(0x60, calldataload(add(offset, argOffset))) 23 | } 24 | } 25 | 26 | /// @notice Reads an immutable arg with type uint256 27 | /// @param argOffset The offset of the arg in the packed data 28 | /// @return arg The arg value 29 | function _getArgUint256(uint256 argOffset) 30 | internal 31 | pure 32 | returns (uint256 arg) 33 | { 34 | uint256 offset = _getImmutableArgsOffset(); 35 | // solhint-disable-next-line no-inline-assembly 36 | assembly { 37 | arg := calldataload(add(offset, argOffset)) 38 | } 39 | } 40 | 41 | /// @notice Reads a uint256 array stored in the immutable args. 42 | /// @param argOffset The offset of the arg in the packed data 43 | /// @param arrLen Number of elements in the array 44 | /// @return arr The array 45 | function _getArgUint256Array(uint256 argOffset, uint256 arrLen) 46 | internal 47 | pure 48 | returns (uint256[] memory arr) 49 | { 50 | uint256 offset = _getImmutableArgsOffset() + argOffset; 51 | arr = new uint256[](arrLen); 52 | 53 | // solhint-disable-next-line no-inline-assembly 54 | assembly { 55 | calldatacopy( 56 | add(arr, ONE_WORD), 57 | offset, 58 | shl(5, arrLen) 59 | ) 60 | } 61 | } 62 | 63 | /// @notice Reads an immutable arg with type uint64 64 | /// @param argOffset The offset of the arg in the packed data 65 | /// @return arg The arg value 66 | function _getArgUint64(uint256 argOffset) 67 | internal 68 | pure 69 | returns (uint64 arg) 70 | { 71 | uint256 offset = _getImmutableArgsOffset(); 72 | // solhint-disable-next-line no-inline-assembly 73 | assembly { 74 | arg := shr(0xc0, calldataload(add(offset, argOffset))) 75 | } 76 | } 77 | 78 | /// @notice Reads an immutable arg with type uint8 79 | /// @param argOffset The offset of the arg in the packed data 80 | /// @return arg The arg value 81 | function _getArgUint8(uint256 argOffset) internal pure returns (uint8 arg) { 82 | uint256 offset = _getImmutableArgsOffset(); 83 | // solhint-disable-next-line no-inline-assembly 84 | assembly { 85 | arg := shr(0xf8, calldataload(add(offset, argOffset))) 86 | } 87 | } 88 | 89 | /// @return offset The offset of the packed immutable args in calldata 90 | function _getImmutableArgsOffset() internal pure returns (uint256 offset) { 91 | // solhint-disable-next-line no-inline-assembly 92 | assembly { 93 | offset := sub( 94 | calldatasize(), 95 | shr(0xf0, calldataload(sub(calldatasize(), 2))) 96 | ) 97 | } 98 | } 99 | } -------------------------------------------------------------------------------- /src/utils/ClonePlus.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD 2 | pragma solidity ^0.8.20; 3 | 4 | import {Clone} from "./Clone.sol"; 5 | import {IERC20} from "oz/token/ERC20/utils/SafeERC20.sol"; 6 | 7 | /// @title ClonePlus 8 | /// @notice Extends Clone with additional helper functions 9 | contract ClonePlus is Clone { 10 | /// @notice Reads a IERC20 array stored in the immutable args. 11 | /// @param argOffset The offset of the arg in the packed data 12 | /// @param arrLen Number of elements in the array 13 | /// @return arr The array 14 | function _getArgIERC20Array(uint256 argOffset, uint256 arrLen) internal pure returns (IERC20[] memory arr) { 15 | uint256 offset = _getImmutableArgsOffset() + argOffset; 16 | arr = new IERC20[](arrLen); 17 | 18 | // solhint-disable-next-line no-inline-assembly 19 | assembly { 20 | calldatacopy(add(arr, ONE_WORD), offset, shl(5, arrLen)) 21 | } 22 | } 23 | 24 | /// @notice Reads a bytes data stored in the immutable args. 25 | /// @param argOffset The offset of the arg in the packed data 26 | /// @param bytesLen Number of bytes in the data 27 | /// @return data the bytes data 28 | function _getArgBytes(uint256 argOffset, uint256 bytesLen) internal pure returns (bytes memory data) { 29 | if (bytesLen == 0) return data; 30 | uint256 offset = _getImmutableArgsOffset() + argOffset; 31 | data = new bytes(bytesLen); 32 | 33 | // solhint-disable-next-line no-inline-assembly 34 | assembly { 35 | calldatacopy(add(data, ONE_WORD), offset, bytesLen) 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /test/Stable2/Well.Stable2.Skim.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {TestHelper, Balances} from "test/TestHelper.sol"; 5 | 6 | contract WellStable2SkimTest is TestHelper { 7 | function setUp() public { 8 | setupStable2Well(); 9 | } 10 | 11 | function test_initialized() public { 12 | // Well should have liquidity 13 | Balances memory wellBalance = getBalances(address(well), well); 14 | assertEq(wellBalance.tokens[0], 1000e18); 15 | assertEq(wellBalance.tokens[1], 1000e18); 16 | } 17 | 18 | function testFuzz_skim(uint256[2] calldata amounts) public prank(user) { 19 | vm.assume(amounts[0] <= 800e18); 20 | vm.assume(amounts[1] <= 800e18); 21 | 22 | // Transfer from Test contract to Well 23 | tokens[0].transfer(address(well), amounts[0]); 24 | tokens[1].transfer(address(well), amounts[1]); 25 | 26 | Balances memory wellBalanceBeforeSkim = getBalances(address(well), well); 27 | // Verify that the Well has received the tokens 28 | assertEq(wellBalanceBeforeSkim.tokens[0], 1000e18 + amounts[0]); 29 | assertEq(wellBalanceBeforeSkim.tokens[1], 1000e18 + amounts[1]); 30 | 31 | // Get a user with a fresh address (no ERC20 tokens) 32 | address _user = users.getNextUserAddress(); 33 | uint256[] memory reserves = new uint256[](2); 34 | 35 | // Verify that the user has no tokens 36 | Balances memory userBalanceBeforeSkim = getBalances(_user, well); 37 | reserves[0] = userBalanceBeforeSkim.tokens[0]; 38 | reserves[1] = userBalanceBeforeSkim.tokens[1]; 39 | assertEq(reserves[0], 0); 40 | assertEq(reserves[1], 0); 41 | 42 | well.skim(_user); 43 | 44 | Balances memory userBalanceAfterSkim = getBalances(_user, well); 45 | Balances memory wellBalanceAfterSkim = getBalances(address(well), well); 46 | 47 | // Since only 1000e18 of each token was added as liquidity, the Well's reserve 48 | // should be reset back to this. 49 | assertEq(wellBalanceAfterSkim.tokens[0], 1000e18); 50 | assertEq(wellBalanceAfterSkim.tokens[1], 1000e18); 51 | 52 | // The difference has been sent to _user. 53 | assertEq(userBalanceAfterSkim.tokens[0], amounts[0]); 54 | assertEq(userBalanceAfterSkim.tokens[1], amounts[1]); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /test/SwapHelper.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.20; 3 | 4 | import {TestHelper, IERC20, Balances, Call, MockToken, Well, console, Snapshot} from "test/TestHelper.sol"; 5 | import {MockFunctionBad} from "mocks/functions/MockFunctionBad.sol"; 6 | import {IWellFunction} from "src/interfaces/IWellFunction.sol"; 7 | 8 | /** 9 | * @dev Stores the expected change in balance for User & Well throughout a Swap. 10 | * 11 | * Gives upstream tests a way to specify expected changes based on the presence 12 | * of transfer fees. When a token involved in a swap incurs a fee on transfer, 13 | * one or both of the following is true: 14 | * 15 | * `wellReceives` < `userSpends` 16 | * `userReceives` < `wellSends` 17 | */ 18 | struct SwapAction { 19 | uint256 i; // input token index 20 | uint256 j; // output token index 21 | uint256 userSpends; 22 | uint256 wellReceives; 23 | uint256 wellSends; 24 | uint256 userReceives; 25 | } 26 | 27 | /** 28 | * @dev Provides common assertions when testing Swaps. 29 | * 30 | * NOTE: Uses globals inherited from TestHelper. 31 | */ 32 | contract SwapHelper is TestHelper { 33 | event AddLiquidity(uint256[] amounts, uint256 lpAmountOut, address recipient); 34 | event Swap(IERC20 fromToken, IERC20 toToken, uint256 amountIn, uint256 amountOut, address recipient); 35 | 36 | /// @dev Default Swap behavior assuming zero fee on transfer 37 | function beforeSwapFrom( 38 | uint256 i, 39 | uint256 j, 40 | uint256 amountIn 41 | ) internal returns (Snapshot memory, SwapAction memory) { 42 | SwapAction memory act; 43 | 44 | act.i = i; 45 | act.j = j; 46 | act.userSpends = amountIn; 47 | act.wellReceives = amountIn; 48 | act.wellSends = well.getSwapOut(tokens[i], tokens[j], amountIn); 49 | act.userReceives = act.wellSends; 50 | 51 | return beforeSwapFrom(act); 52 | } 53 | 54 | function beforeSwapFrom(SwapAction memory act) internal returns (Snapshot memory, SwapAction memory) { 55 | Snapshot memory bef = _newSnapshot(); 56 | 57 | vm.expectEmit(true, true, true, true, address(well)); 58 | emit Swap(tokens[act.i], tokens[act.j], act.wellReceives, act.wellSends, user); 59 | 60 | return (bef, act); 61 | } 62 | 63 | function afterSwapFrom(Snapshot memory bef, SwapAction memory act) public { 64 | Snapshot memory aft = _newSnapshot(); 65 | uint256 i = act.i; 66 | uint256 j = act.j; 67 | 68 | // Check balances accounting 69 | assertEq(bef.user.tokens[i] - aft.user.tokens[i], act.userSpends, "Incorrect token[i] User balance"); 70 | assertEq(aft.well.tokens[i], bef.well.tokens[i] + act.wellReceives, "Incorrect token[i] Well balance"); 71 | assertEq(aft.well.tokens[j], bef.well.tokens[j] - act.wellSends, "Incorrect token[j] Well balance"); 72 | assertEq(aft.user.tokens[j] - bef.user.tokens[j], act.userReceives, "Incorrect token[j] User balance"); 73 | 74 | // Check that reserves were updated 75 | uint256[] memory reserves = well.getReserves(); 76 | assertEq(aft.reserves[i], bef.reserves[i] + act.wellReceives, "Incorrect token[i] Well reserve"); 77 | assertEq(aft.reserves[j], bef.reserves[i] - act.wellSends, "Incorrect token[i] Well reserve"); 78 | 79 | // Check that no other balances or reserves were changed 80 | for (uint256 k = 0; k < reserves.length; ++k) { 81 | if (k == i || k == j) continue; 82 | assertEq(aft.well.tokens[k], bef.well.tokens[k], "token[k] Well balance changed unexpectedly"); 83 | assertEq(aft.reserves[k], bef.reserves[k], "token[k] Well reserve changed unexpectedly"); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /test/Well.DuplicateTokens.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.20; 3 | 4 | import {console, Aquifer, IERC20, TestHelper} from "test/TestHelper.sol"; 5 | import {LibWellConstructor} from "src/libraries/LibWellConstructor.sol"; 6 | import {IWell} from "src/interfaces/IWell.sol"; 7 | import {IAquifer} from "src/interfaces/IAquifer.sol"; 8 | 9 | contract WellDuplicateTokens is TestHelper { 10 | function test_fail_on_duplicate_tokens() public { 11 | tokens = new IERC20[](2); 12 | tokens[0] = deployMockToken(0); 13 | tokens[1] = tokens[0]; 14 | initUser(); 15 | wellImplementation = deployWellImplementation(); 16 | wellFunction = deployWellFunction(); 17 | aquifer = new Aquifer(); 18 | (bytes memory immutableData, bytes memory initData) = 19 | LibWellConstructor.encodeWellDeploymentData(address(aquifer), tokens, wellFunction, pumps); 20 | 21 | vm.expectRevert(abi.encodeWithSelector(IAquifer.InitFailed.selector, "")); 22 | Aquifer(aquifer).boreWell(wellImplementation, immutableData, initData, bytes32(0)); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test/Well.FeeOnTransfer.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.20; 4 | 5 | import {TestHelper, IERC20, Call, Balances, MockTokenFeeOnTransfer} from "test/TestHelper.sol"; 6 | import {ConstantProduct2} from "src/functions/ConstantProduct2.sol"; 7 | import {IWell} from "src/interfaces/IWell.sol"; 8 | import {IWellErrors} from "src/interfaces/IWellErrors.sol"; 9 | 10 | /** 11 | * @dev Functions that increase the Well's reserves without accounting for fees 12 | * (swapFrom, swapTo, addLiquidity) should revert if there actually are fees. 13 | */ 14 | contract WellFeeOnTransferTest is TestHelper { 15 | event AddLiquidity(uint256[] tokenAmountsIn, uint256 lpAmountOut, address recipient); 16 | event RemoveLiquidity(uint256 lpAmountIn, uint256[] tokenAmountsOut, address recipient); 17 | 18 | function setUp() public { 19 | setupWellWithFeeOnTransfer(2); 20 | MockTokenFeeOnTransfer(address(tokens[0])).setFee(1e16); 21 | } 22 | 23 | function test_swapTo_feeOnTransfer() public prank(user) { 24 | uint256 minAmountOut = 500 * 1e18; 25 | uint256 amountIn = 1000 * 1e18; 26 | 27 | vm.expectRevert(IWellErrors.InvalidReserves.selector); 28 | well.swapTo(tokens[0], tokens[1], amountIn, minAmountOut, user, type(uint256).max); 29 | } 30 | 31 | function test_swapFrom_feeOnTransfer() public prank(user) { 32 | uint256 minAmountOut = 500 * 1e18; 33 | uint256 amountIn = 1000 * 1e18; 34 | 35 | vm.expectRevert(IWellErrors.InvalidReserves.selector); 36 | well.swapFrom(tokens[0], tokens[1], amountIn, minAmountOut, user, type(uint256).max); 37 | } 38 | 39 | function test_addLiquidity_feeOnTransfer() public prank(user) { 40 | uint256[] memory amounts = new uint256[](tokens.length); 41 | 42 | for (uint256 i; i < tokens.length; i++) { 43 | amounts[i] = 1000 * 1e18; 44 | } 45 | uint256 lpAmountOut = 1000 * 1e24; 46 | 47 | vm.expectRevert(IWellErrors.InvalidReserves.selector); 48 | well.addLiquidity(amounts, lpAmountOut, user, type(uint256).max); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /test/Well.ReadOnlyReentrancy.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.20; 3 | 4 | import {IERC20, Well, Strings} from "test/TestHelper.sol"; 5 | import {SwapHelper} from "test/SwapHelper.sol"; 6 | import {ReentrantMockToken} from "mocks/tokens/ReentrantMockToken.sol"; 7 | import {IWell} from "src/interfaces/IWell.sol"; 8 | 9 | contract WellReadOnlyReentrancyTest is SwapHelper { 10 | using Strings for uint256; 11 | 12 | ReentrantMockToken[] _tokens; 13 | uint256[] amounts; 14 | 15 | function setUp() public { 16 | uint256 _numberOfTokens = 2; 17 | _tokens = new ReentrantMockToken[](_numberOfTokens); 18 | tokens = new IERC20[](_numberOfTokens); 19 | for (uint256 i; i < _numberOfTokens; i++) { 20 | _tokens[i] = new ReentrantMockToken( 21 | string.concat("Token ", i.toString()), // name 22 | string.concat("TOKEN", i.toString()), // symbol 23 | 18 // decimals 24 | ); 25 | tokens[i] = IERC20(_tokens[i]); 26 | } 27 | setupWell(deployWellFunction(), deployPumps(1), tokens); 28 | 29 | amounts = new uint256[](tokens.length); 30 | for (uint256 i; i < tokens.length; i++) { 31 | amounts[i] = 1000 * 1e18; 32 | } 33 | } 34 | 35 | function _checkReadOnlyReentrancy() internal { 36 | vm.expectRevert("ReentrancyGuard: reentrant call"); 37 | well.addLiquidity(amounts, 0, user, type(uint256).max); 38 | } 39 | 40 | function test_readOnlyReentrancy_getReserves() public prank(user) { 41 | _tokens[0].setCall(address(well), abi.encode(IWell.getReserves.selector)); 42 | _checkReadOnlyReentrancy(); 43 | } 44 | 45 | function test_readOnlyReentrancy_getSwapOut() public prank(user) { 46 | _tokens[0].setCall( 47 | address(well), abi.encodeWithSelector(IWell.getSwapOut.selector, tokens[0], tokens[1], amounts[0]) 48 | ); 49 | _checkReadOnlyReentrancy(); 50 | } 51 | 52 | function test_readOnlyReentrancy_getSwapIn() public prank(user) { 53 | _tokens[0].setCall( 54 | address(well), abi.encodeWithSelector(IWell.getSwapIn.selector, tokens[0], tokens[1], amounts[0]) 55 | ); 56 | _checkReadOnlyReentrancy(); 57 | } 58 | 59 | function test_readOnlyReentrancy_getAddLiquidityOut() public prank(user) { 60 | _tokens[0].setCall(address(well), abi.encodeWithSelector(IWell.getAddLiquidityOut.selector, amounts)); 61 | _checkReadOnlyReentrancy(); 62 | } 63 | 64 | function test_readOnlyReentrancy_getRemoveLiquidityOut() public prank(user) { 65 | _tokens[0].setCall(address(well), abi.encodeWithSelector(IWell.getRemoveLiquidityOut.selector, amounts[0])); 66 | _checkReadOnlyReentrancy(); 67 | } 68 | 69 | function test_readOnlyReentrancy_getRemoveLiquidityOneTokenOut() public prank(user) { 70 | _tokens[0].setCall( 71 | address(well), abi.encodeWithSelector(IWell.getRemoveLiquidityOneTokenOut.selector, amounts[0], tokens[0]) 72 | ); 73 | _checkReadOnlyReentrancy(); 74 | } 75 | 76 | function test_readOnlyReentrancy_getRemoveLiquidityImbalancedIn() public prank(user) { 77 | _tokens[0].setCall( 78 | address(well), abi.encodeWithSelector(IWell.getRemoveLiquidityImbalancedIn.selector, amounts) 79 | ); 80 | _checkReadOnlyReentrancy(); 81 | } 82 | 83 | function test_readOnlyReentrancy_getShiftOut() public prank(user) { 84 | _tokens[0].setCall(address(well), abi.encodeWithSelector(IWell.getShiftOut.selector, tokens[0])); 85 | _checkReadOnlyReentrancy(); 86 | } 87 | 88 | function test_readOnlyReentrancy_getSyncOut() public prank(user) { 89 | _tokens[0].setCall(address(well), abi.encode(IWell.getSyncOut.selector)); 90 | _checkReadOnlyReentrancy(); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /test/Well.Skim.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.20; 3 | 4 | import {TestHelper, Balances} from "test/TestHelper.sol"; 5 | 6 | contract WellSkimStableSwapTest is TestHelper { 7 | function setUp() public { 8 | setupWell(2); 9 | } 10 | 11 | function test_initialized() public { 12 | // Well should have liquidity 13 | Balances memory wellBalance = getBalances(address(well), well); 14 | assertEq(wellBalance.tokens[0], 1000e18); 15 | assertEq(wellBalance.tokens[1], 1000e18); 16 | } 17 | 18 | function testFuzz_skim(uint256[2] calldata amounts) public prank(user) { 19 | vm.assume(amounts[0] <= 800e18); 20 | vm.assume(amounts[1] <= 800e18); 21 | 22 | // Transfer from Test contract to Well 23 | tokens[0].transfer(address(well), amounts[0]); 24 | tokens[1].transfer(address(well), amounts[1]); 25 | 26 | Balances memory wellBalanceBeforeSkim = getBalances(address(well), well); 27 | // Verify that the Well has received the tokens 28 | assertEq(wellBalanceBeforeSkim.tokens[0], 1000e18 + amounts[0]); 29 | assertEq(wellBalanceBeforeSkim.tokens[1], 1000e18 + amounts[1]); 30 | 31 | // Get a user with a fresh address (no ERC20 tokens) 32 | address _user = users.getNextUserAddress(); 33 | uint256[] memory reserves = new uint256[](2); 34 | 35 | // Verify that the user has no tokens 36 | Balances memory userBalanceBeforeSkim = getBalances(_user, well); 37 | reserves[0] = userBalanceBeforeSkim.tokens[0]; 38 | reserves[1] = userBalanceBeforeSkim.tokens[1]; 39 | assertEq(reserves[0], 0); 40 | assertEq(reserves[1], 0); 41 | 42 | well.skim(_user); 43 | 44 | Balances memory userBalanceAfterSkim = getBalances(_user, well); 45 | Balances memory wellBalanceAfterSkim = getBalances(address(well), well); 46 | 47 | // Since only 1000e18 of each token was added as liquidity, the Well's reserve 48 | // should be reset back to this. 49 | assertEq(wellBalanceAfterSkim.tokens[0], 1000e18); 50 | assertEq(wellBalanceAfterSkim.tokens[1], 1000e18); 51 | 52 | // The difference has been sent to _user. 53 | assertEq(userBalanceAfterSkim.tokens[0], amounts[0]); 54 | assertEq(userBalanceAfterSkim.tokens[1], amounts[1]); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /test/Well.SucceedOnPumpFailure.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.20; 3 | 4 | import {TestHelper, Balances, Call} from "test/TestHelper.sol"; 5 | import {MockFailPump} from "mocks/pumps/MockFailPump.sol"; 6 | 7 | contract WellSucceedOnPumpFailure is TestHelper { 8 | MockFailPump _pump; 9 | 10 | function setUp() public { 11 | _pump = new MockFailPump(); 12 | } 13 | 14 | // Check that the pump function fails as expected 15 | function test_fail() public { 16 | vm.expectRevert(); 17 | uint256[] memory amounts = new uint256[](2); 18 | _pump.update(amounts, new bytes(0)); 19 | } 20 | 21 | // Check that the Well doesn't fail if one of one fail 22 | function test_addLiquidty_onePump() public { 23 | Call[] memory pumps = new Call[](1); 24 | pumps[0].target = address(_pump); 25 | setupWell(2, pumps); 26 | // Check that the add liquidity call succeeded during well setup. 27 | assertGt(well.totalSupply(), 0); 28 | } 29 | 30 | // Check that the Well doesn't fail if m of n pumps fail 31 | function test_addLiquidty_twoPumps() public { 32 | Call[] memory pumps = new Call[](2); 33 | pumps[0].target = address(_pump); 34 | pumps[1].target = address(new MockFailPump()); 35 | setupWell(2, pumps); 36 | // Check that the add liquidity call succeeded during well setup. 37 | assertGt(well.totalSupply(), 0); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /test/Well.SwapFrom.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.20; 3 | 4 | import {IERC20, Balances, Call, MockToken, Well, console} from "test/TestHelper.sol"; 5 | import {SwapHelper, SwapAction, Snapshot} from "test/SwapHelper.sol"; 6 | import {MockFunctionBad} from "mocks/functions/MockFunctionBad.sol"; 7 | import {IWellFunction} from "src/interfaces/IWellFunction.sol"; 8 | import {IWell} from "src/interfaces/IWell.sol"; 9 | import {IWellErrors} from "src/interfaces/IWellErrors.sol"; 10 | 11 | contract WellSwapFromTest is SwapHelper { 12 | function setUp() public { 13 | setupWell(2); 14 | } 15 | 16 | function test_getSwapOut() public { 17 | uint256 amountIn = 1000 * 1e18; 18 | uint256 amountOut = well.getSwapOut(tokens[0], tokens[1], amountIn); 19 | 20 | assertEq(amountOut, 500 * 1e18); 21 | } 22 | 23 | function testFuzz_getSwapOut_revertIf_insufficientWellBalance(uint256 amountIn, uint256 i) public prank(user) { 24 | // Swap token `i` -> all other tokens 25 | i = bound(i, 0, tokens.length); 26 | 27 | // Find an input amount that produces an output amount higher than what the Well has. 28 | // When the Well is deployed it has zero reserves, so any nonzero value should revert. 29 | amountIn = bound(amountIn, 1, type(uint128).max); 30 | 31 | // Deploy a new Well with a poorly engineered pricing function. 32 | // Its `getBalance` function can return an amount greater than the Well holds. 33 | IWellFunction badFunction = new MockFunctionBad(); 34 | Well badWell = encodeAndBoreWell( 35 | address(aquifer), wellImplementation, tokens, Call(address(badFunction), ""), pumps, bytes32(0) 36 | ); 37 | 38 | // Check assumption that reserves are empty 39 | Balances memory wellBalances = getBalances(address(badWell), badWell); 40 | assertEq(wellBalances.tokens[0], 0, "bad assumption: wellBalances.tokens[0] != 0"); 41 | assertEq(wellBalances.tokens[1], 0, "bad assumption: wellBalances.tokens[1] != 0"); 42 | 43 | for (uint256 j; j < tokens.length; ++j) { 44 | if (j != i) { 45 | vm.expectRevert(); // underflow 46 | badWell.getSwapOut(tokens[i], tokens[j], amountIn); 47 | } 48 | } 49 | } 50 | 51 | /// @dev Swaps should always revert if `fromToken` = `toToken`. 52 | function test_swapFrom_revertIf_sameToken() public prank(user) { 53 | vm.expectRevert(IWellErrors.InvalidTokens.selector); 54 | well.swapFrom(tokens[0], tokens[0], 100 * 1e18, 0, user, type(uint256).max); 55 | } 56 | 57 | /// @dev Slippage revert if minAmountOut is too high 58 | function test_swapFrom_revertIf_minAmountOutTooHigh() public prank(user) { 59 | uint256 amountIn = 1000 * 1e18; 60 | uint256 minAmountOut = 501 * 1e18; // actual: 500 61 | uint256 amountOut = 500 * 1e18; 62 | 63 | vm.expectRevert(abi.encodeWithSelector(IWellErrors.SlippageOut.selector, amountOut, minAmountOut)); 64 | well.swapFrom(tokens[0], tokens[1], amountIn, minAmountOut, user, type(uint256).max); 65 | } 66 | 67 | function test_swapFrom_revertIf_expired() public { 68 | vm.expectRevert(IWellErrors.Expired.selector); 69 | well.swapFrom(tokens[0], tokens[1], 0, 0, user, block.timestamp - 1); 70 | } 71 | 72 | function testFuzz_swapFrom(uint256 amountIn) public prank(user) { 73 | amountIn = bound(amountIn, 0, tokens[0].balanceOf(user)); 74 | 75 | (Snapshot memory bef, SwapAction memory act) = beforeSwapFrom(0, 1, amountIn); 76 | act.wellSends = well.swapFrom(tokens[0], tokens[1], amountIn, 0, user, type(uint256).max); 77 | afterSwapFrom(bef, act); 78 | checkInvariant(address(well)); 79 | } 80 | 81 | /// @dev Zero hysteresis: token0 -> token1 -> token0 gives the same result 82 | function testFuzz_swapFrom_equalSwap(uint256 token0AmtIn) public prank(user) { 83 | token0AmtIn = bound(token0AmtIn, 0, tokens[0].balanceOf(user)); 84 | uint256 token1Out = well.swapFrom(tokens[0], tokens[1], token0AmtIn, 0, user, type(uint256).max); 85 | uint256 token0Out = well.swapFrom(tokens[1], tokens[0], token1Out, 0, user, type(uint256).max); 86 | assertEq(token0Out, token0AmtIn); 87 | checkInvariant(address(well)); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /test/Well.SwapFromFeeOnTransfer.NoFee.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.20; 3 | 4 | import {TestHelper, IERC20, Balances, Call, MockToken, Well} from "test/TestHelper.sol"; 5 | import {SwapHelper, SwapAction, Snapshot} from "test/SwapHelper.sol"; 6 | import {MockFunctionBad} from "mocks/functions/MockFunctionBad.sol"; 7 | import {IWellFunction} from "src/interfaces/IWellFunction.sol"; 8 | import {IWell} from "src/interfaces/IWell.sol"; 9 | import {IWellErrors} from "src/interfaces/IWellErrors.sol"; 10 | 11 | /** 12 | * @dev Tests {swapFromFeeOnTransfer} when tokens involved in the swap DO NOT 13 | * incur a fee on transfer. 14 | */ 15 | contract WellSwapFromFeeOnTransferNoFeeTest is SwapHelper { 16 | function setUp() public { 17 | setupWell(2); 18 | } 19 | 20 | /// @dev Slippage revert if minAmountOut is too high. 21 | function test_swapFromFeeOnTransferNoFee_revertIf_minAmountOutTooHigh() public prank(user) { 22 | uint256 amountIn = 1000 * 1e18; 23 | uint256 minAmountOut = 501 * 1e18; // actual: 500 24 | uint256 amountOut = 500 * 1e18; 25 | 26 | vm.expectRevert(abi.encodeWithSelector(IWellErrors.SlippageOut.selector, amountOut, minAmountOut)); 27 | well.swapFromFeeOnTransfer(tokens[0], tokens[1], amountIn, minAmountOut, user, type(uint256).max); 28 | } 29 | 30 | /// @dev Swaps should always revert if `fromToken` = `toToken`. 31 | function testFuzz_swapFromFeeOnTransferNoFee_revertIf_sameToken(uint128 amountIn) public prank(user) { 32 | MockToken(address(tokens[0])).mint(user, amountIn); 33 | 34 | vm.expectRevert(IWellErrors.InvalidTokens.selector); 35 | well.swapFromFeeOnTransfer(tokens[0], tokens[0], amountIn, 0, user, type(uint256).max); 36 | } 37 | 38 | /// @dev Note: this covers the case where there is a fee as well 39 | function test_swapFromFeeOnTransferNoFee_revertIf_expired() public { 40 | vm.expectRevert(IWellErrors.Expired.selector); 41 | well.swapFromFeeOnTransfer(tokens[0], tokens[1], 0, 0, user, block.timestamp - 1); 42 | } 43 | 44 | /// @dev With no fees, behavior is identical to {swapFrom}. 45 | function testFuzz_swapFromFeeOnTransfer_noFee(uint256 amountIn) public prank(user) { 46 | amountIn = bound(amountIn, 0, tokens[0].balanceOf(user)); 47 | 48 | (Snapshot memory bef, SwapAction memory act) = beforeSwapFrom(0, 1, amountIn); 49 | well.swapFromFeeOnTransfer(tokens[0], tokens[1], amountIn, act.userReceives, user, type(uint256).max); 50 | afterSwapFrom(bef, act); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /test/Well.Tokens.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.20; 4 | 5 | import {Test, console} from "forge-std/Test.sol"; 6 | import {Well, IERC20} from "test/TestHelper.sol"; 7 | 8 | contract WellInternalTest is Well, Test { 9 | IERC20[] _tokens; 10 | IERC20 token0; 11 | IERC20 token1; 12 | IERC20 tokenMissing1; 13 | IERC20 tokenMissing2; 14 | 15 | function setUp() public { 16 | token0 = IERC20(address(1)); 17 | token1 = IERC20(address(2)); 18 | tokenMissing1 = IERC20(address(bytes20("not in well"))); 19 | tokenMissing2 = IERC20(address(bytes20("also not in well"))); 20 | 21 | _tokens = new IERC20[](2); 22 | _tokens[0] = IERC20(token0); 23 | _tokens[1] = IERC20(token1); 24 | } 25 | 26 | function test_getIJ() public { 27 | (uint256 i, uint256 j) = _getIJ(_tokens, token0, token1); 28 | assertEq(i, 0); 29 | assertEq(j, 1); 30 | 31 | (i, j) = _getIJ(_tokens, token1, token0); 32 | assertEq(i, 1); 33 | assertEq(j, 0); 34 | } 35 | 36 | function testFuzz_getIJ(uint256 n) public { 37 | n = bound(n, 2, 16); 38 | 39 | _tokens = new IERC20[](n); 40 | for (uint256 i = 1; i <= n; ++i) { 41 | _tokens[i - 1] = IERC20(address(uint160(i))); 42 | } 43 | 44 | // Check all combinations of tokens 45 | for (uint256 i; i < n; ++i) { 46 | for (uint256 j; j < n; ++j) { 47 | if (i == j) { 48 | vm.expectRevert(InvalidTokens.selector); 49 | } 50 | (uint256 i_, uint256 j_) = _getIJ(_tokens, _tokens[i], _tokens[j]); 51 | if (i != j) { 52 | assertEq(i_, i, "i"); 53 | assertEq(j_, j, "j"); 54 | } 55 | } 56 | } 57 | } 58 | 59 | function test_getIJ_revertIfIdentical() public { 60 | vm.expectRevert(InvalidTokens.selector); 61 | _getIJ(_tokens, token0, token0); 62 | } 63 | 64 | function test_getIJ_revertIfOneMissing() public { 65 | vm.expectRevert(InvalidTokens.selector); 66 | _getIJ(_tokens, tokenMissing1, token0); // i is missing 67 | 68 | vm.expectRevert(InvalidTokens.selector); 69 | _getIJ(_tokens, token0, tokenMissing1); // j is missing 70 | } 71 | 72 | function test_getIJ_revertIfBothMissing() public { 73 | vm.expectRevert(InvalidTokens.selector); 74 | _getIJ(_tokens, tokenMissing1, tokenMissing2); 75 | } 76 | 77 | function test_getJ() public { 78 | uint256 i = _getJ(_tokens, token0); 79 | assertEq(i, 0); 80 | 81 | uint256 j = _getJ(_tokens, token1); 82 | assertEq(j, 1); 83 | } 84 | 85 | function test_getJ_revertIfMissing() public { 86 | vm.expectRevert(InvalidTokens.selector); 87 | _getJ(_tokens, tokenMissing1); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /test/Well.UpdatePump.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.20; 3 | 4 | import {TestHelper, Call, MockPump} from "test/TestHelper.sol"; 5 | import {MockEmptyFunction} from "mocks/functions/MockEmptyFunction.sol"; 6 | 7 | contract WellUpdatePumpTest is TestHelper { 8 | function setUp() public { 9 | wellFunction = Call(address(new MockEmptyFunction()), ""); 10 | } 11 | 12 | function test_updatePump(uint8 numPumps, bytes[8] memory pumpBytes) public { 13 | vm.assume(numPumps <= 8); 14 | for (uint256 i; i < numPumps; i++) { 15 | vm.assume(pumpBytes[i].length <= 8 * 32); 16 | } 17 | 18 | // Create `numPumps` Call structs 19 | Call[] memory pumps = new Call[](numPumps); 20 | for (uint256 i; i < numPumps; i++) { 21 | pumps[i].target = address(new MockPump()); 22 | pumps[i].data = pumpBytes[i]; 23 | } 24 | 25 | // Setup a Well with multiple pumps and test each 26 | // NOTE: this works because liquidity is deployed which switches 27 | // lastData from "0xATTACHED" to the `data` param which is passed during 28 | // the `update()` call. If liquidity is not added, this will fail. 29 | setupWell(2, wellFunction, pumps); 30 | 31 | // Perform an action on the Well to initialize pumps 32 | vm.prank(user); 33 | well.swapFrom(tokens[0], tokens[1], 1e18, 1, user, type(uint256).max); 34 | 35 | // During update(), MockPump sets a public storage var `lastData` equal 36 | // to Call.data. 37 | for (uint256 i; i < numPumps; i++) { 38 | assertEq(pumps[i].data, MockPump(pumps[i].target).lastData()); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /test/differential/ConstantProduct.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | EXP_PRECISION = 1e12 4 | 5 | class ConstantProduct: 6 | 7 | def __init__(self): 8 | pass 9 | 10 | def calcLPTokenUnderlying( 11 | self, 12 | lpTokenAmount, 13 | reserves, 14 | lpTokenSupply 15 | ): 16 | return [lpTokenAmount * r / lpTokenSupply for r in reserves] 17 | 18 | def calcLpTokenSupply( 19 | self, 20 | reserves 21 | ): 22 | return math.sqrt(reserves[0] * reserves[1] * EXP_PRECISION) 23 | 24 | def calcReserve( 25 | self, 26 | reserves, 27 | j, 28 | lpTokenSupply 29 | ): 30 | return (lpTokenSupply ** 2) / (reserves[0 if j == 1 else 1] * EXP_PRECISION) 31 | 32 | def calcReserveAtRatioSwap( 33 | self, 34 | reserves, 35 | j, 36 | ratios 37 | ): 38 | i = 0 if j == 1 else 1 39 | return math.sqrt((reserves[i] * reserves[j]) * (ratios[j] / ratios[i])) 40 | 41 | def calcReserveAtRatioLiquidity( 42 | self, 43 | reserves, 44 | j, 45 | ratios 46 | ): 47 | i = 0 if j == 1 else 1 48 | return reserves[i] * ratios[j] / ratios[i] 49 | 50 | def calcRate( 51 | self, 52 | reserves, 53 | i, 54 | j 55 | ): 56 | return reserves[i] / reserves[j] -------------------------------------------------------------------------------- /test/differential/powu.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | from eth_abi import encode 3 | from decimal import * 4 | getcontext().prec = 40 5 | 6 | def powuFraction(num, denom, exp): 7 | return (Decimal(num)/Decimal(denom))**Decimal(exp) 8 | 9 | def main(args): 10 | powu = powuFraction(args.numerator, args.denominator, args.exponent) * (2**128) 11 | powu_enc = encode(['int256'], [int(powu)]) 12 | print("0x" + powu_enc.hex()) 13 | 14 | # def test(args): 15 | # powu_fraction = powuFraction(args.numerator, args.denominator, args.exponent) 16 | # powu = powu_fraction * (2**128) 17 | # print(powu_fraction) 18 | # print('{:f}'.format(powu_fraction)) 19 | # print(powu) 20 | 21 | def parse_args(): 22 | parser = argparse.ArgumentParser() 23 | parser.add_argument("--numerator", "-n", type=int) 24 | parser.add_argument("--denominator", "-d", type=int) 25 | parser.add_argument("--exponent", "-e", type=int) 26 | return parser.parse_args() 27 | 28 | if __name__ == '__main__': 29 | args = parse_args() 30 | main(args) 31 | # test(args) -------------------------------------------------------------------------------- /test/functions/ConstantProduct.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.20; 3 | 4 | import {WellFunctionHelper} from "./WellFunctionHelper.sol"; 5 | import {ConstantProduct} from "src/functions/ConstantProduct.sol"; 6 | 7 | contract ConstantProductTest is WellFunctionHelper { 8 | function setUp() public { 9 | _function = new ConstantProduct(); 10 | _data = ""; 11 | } 12 | 13 | function test_name() public { 14 | assertEq(_function.name(), "Constant Product"); 15 | assertEq(_function.symbol(), "CP"); 16 | } 17 | 18 | //////////// LP TOKEN SUPPLY //////////// 19 | 20 | /// @dev calcLpTokenSupply: `n` equal reserves should summate with the token supply 21 | function testLpTokenSupplySmall(uint256 n) public { 22 | n = bound(n, 2, 15); 23 | uint256[] memory reserves = new uint256[](n); 24 | for (uint256 i; i < n; ++i) { 25 | reserves[i] = 1; 26 | } 27 | assertEq(_function.calcLpTokenSupply(reserves, _data), 1 * n); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/functions/WellFunctionHelper.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.20; 3 | 4 | import {TestHelper, console, stdError} from "test/TestHelper.sol"; 5 | import {IMultiFlowPumpWellFunction} from "src/interfaces/IMultiFlowPumpWellFunction.sol"; 6 | 7 | /// @dev Provides a base test suite for all Well functions. 8 | abstract contract WellFunctionHelper is TestHelper { 9 | IMultiFlowPumpWellFunction _function; 10 | bytes _data; 11 | 12 | /// @dev calcLpTokenSupply: 0 reserves = 0 supply 13 | /// Some Well Functions will choose to support > 2 tokens. 14 | /// Additional tokens passed in `reserves` should be ignored. 15 | function test_calcLpTokenSupply_empty(uint256 n) public { 16 | n = bound(n, 2, 15); 17 | uint256[] memory reserves = new uint256[](n); 18 | assertEq(_function.calcLpTokenSupply(reserves, _data), 0); 19 | } 20 | 21 | /// @dev require at least `n` reserves to be passed to `calcLpTokenSupply` 22 | function check_calcLpTokenSupply_minBalancesLength(uint256 n) public { 23 | for (uint256 i; i < n; ++i) { 24 | vm.expectRevert(stdError.indexOOBError); // "Index out of bounds" 25 | _function.calcLpTokenSupply(new uint256[](i), _data); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /test/helpers/Users.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.7.6; 4 | 5 | import {Test} from "forge-std/Test.sol"; 6 | 7 | //common utilities for forge tests 8 | contract Users is Test { 9 | bytes32 internal nextUser = keccak256(abi.encodePacked("user address")); 10 | 11 | function getNextUserAddress() external returns (address) { 12 | //bytes32 to address conversion 13 | address user = address(uint160(uint256(nextUser))); 14 | nextUser = keccak256(abi.encodePacked(nextUser)); 15 | vm.deal(user, 100 ether); 16 | return user; 17 | } 18 | 19 | //create users with 100 ether balance 20 | function createUsers(uint256 userNum) external returns (address[] memory) { 21 | address[] memory users = new address[](userNum); 22 | for (uint256 i; i < userNum; i++) { 23 | address user = this.getNextUserAddress(); 24 | users[i] = user; 25 | } 26 | return users; 27 | } 28 | 29 | //move block.number forward by a given number of blocks 30 | function mineBlocks(uint256 numBlocks) external { 31 | uint256 targetBlock = block.number + numBlocks; 32 | vm.roll(targetBlock); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test/integration/GasMetering.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.20; 3 | 4 | import {Test, console, stdError} from "forge-std/Test.sol"; 5 | 6 | contract TestGasMetering is Test { 7 | //////////////////// GAS METERING //////////////////// 8 | 9 | /// @dev invariant test: pausing gas metering should always result 10 | /// in a smaller delta. 11 | function test_invariant_pauseGasMetering() public { 12 | // Expect no change once inside paused gas metering 13 | vm.pauseGasMetering(); 14 | uint256 start1 = gasleft(); 15 | { 16 | for (uint256 i; i < 10; i++) { 17 | uint256 gasnow = gasleft(); 18 | assertEq(gasnow, start1); 19 | } 20 | } 21 | uint256 end1 = gasleft(); 22 | vm.resumeGasMetering(); 23 | assertEq(start1, end1, "Gas metering invariant: gas changed while paused"); 24 | 25 | uint256 start2 = gasleft(); 26 | { 27 | uint256 hold2 = gasleft(); 28 | for (uint256 i; i < 10; i++) { 29 | uint256 gasnow = gasleft(); 30 | assertTrue(gasnow < start2); 31 | assertTrue(gasnow < hold2); 32 | hold2 = gasnow; 33 | } 34 | } 35 | uint256 end2 = gasleft(); 36 | 37 | // (start1 - end1) is zero given previous assertion 38 | assertTrue((start1 - end1) < (start2 - end2), "Gas metering invariant: delta"); 39 | } 40 | 41 | function test_gasleft_cost() public { 42 | uint256 start = gasleft(); 43 | uint256 hold = gasleft(); 44 | uint256 end = gasleft(); 45 | 46 | console.log("start - end", start, end, start - end); 47 | console.log("start - hold", start, hold, start - hold); 48 | console.log("hold - end", hold, end, hold - end); 49 | 50 | assertTrue(true); 51 | } 52 | 53 | function test_pauseGasMetering_cost() public { 54 | uint256 start = gasleft(); 55 | vm.pauseGasMetering(); 56 | uint256 hold = gasleft(); 57 | vm.resumeGasMetering(); 58 | uint256 end = gasleft(); 59 | 60 | console.log("pauseGasMetering cost:", start - end); 61 | console.log("resumeGasMetering cost:", hold - end); 62 | 63 | assertTrue(true); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /test/integration/interfaces/IPipeline.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.20; 3 | 4 | import {IERC20} from "test/integration/IntegrationTestHelper.sol"; 5 | 6 | /** 7 | * @title IPipeline 8 | * @author Brendan 9 | * @notice Pipeline Interface – Pipeline creates a sandbox to execute any series of function calls on any series of protocols through \term{Pipe} functions. 10 | * Any assets left in Pipeline between transactions can be transferred out by any account. 11 | * Users Pipe a series of PipeCalls that each execute a function call to another protocol through Pipeline. 12 | * 13 | */ 14 | 15 | // PipeCalls specify a function call to be executed by Pipeline. 16 | // Pipeline supports 2 types of PipeCalls: PipeCall and AdvancedPipeCall. 17 | 18 | // PipeCall makes a function call with a static target address and callData. 19 | struct PipeCall { 20 | address target; 21 | bytes data; 22 | } 23 | 24 | // AdvancedPipeCall makes a function call with a static target address and both static and dynamic callData. 25 | // AdvancedPipeCalls support sending Ether in calls. 26 | // [ PipeCall Type | Send Ether Flag | PipeCall Type data | Ether Value (only if flag == 1)] 27 | // [ 1 byte | 1 byte | n bytes | 0 or 32 bytes ] 28 | // See LibFunction.useClipboard for more details. 29 | struct AdvancedPipeCall { 30 | address target; 31 | bytes callData; 32 | bytes clipboard; 33 | } 34 | 35 | interface IPipeline { 36 | function pipe(PipeCall calldata p) external payable returns (bytes memory result); 37 | 38 | function multiPipe(PipeCall[] calldata pipes) external payable returns (bytes[] memory results); 39 | 40 | function advancedPipe(AdvancedPipeCall[] calldata pipes) external payable returns (bytes[] memory results); 41 | } 42 | 43 | interface IDepot { 44 | function advancedPipe( 45 | AdvancedPipeCall[] calldata pipes, 46 | uint256 value 47 | ) external payable returns (bytes[] memory results); 48 | 49 | function farm(bytes[] calldata data) external payable returns (bytes[] memory results); 50 | 51 | function transferToken( 52 | IERC20 token, 53 | address recipient, 54 | uint256 amount, 55 | From fromMode, 56 | To toMode 57 | ) external payable; 58 | } 59 | 60 | enum From { 61 | EXTERNAL, 62 | INTERNAL, 63 | EXTERNAL_INTERNAL, 64 | INTERNAL_TOLERANT 65 | } 66 | 67 | enum To { 68 | EXTERNAL, 69 | INTERNAL 70 | } 71 | 72 | interface IBeanstalk { 73 | function transferInternalTokenFrom( 74 | IERC20 token, 75 | address sender, 76 | address recipient, 77 | uint256 amount, 78 | To toMode 79 | ) external payable; 80 | 81 | function permitToken( 82 | address owner, 83 | address spender, 84 | address token, 85 | uint256 value, 86 | uint256 deadline, 87 | uint8 v, 88 | bytes32 r, 89 | bytes32 s 90 | ) external payable; 91 | 92 | function transferDeposit( 93 | address sender, 94 | address recipient, 95 | address token, 96 | uint32 season, 97 | uint256 amount 98 | ) external payable returns (uint256 bdv); 99 | 100 | function transferDeposits( 101 | address sender, 102 | address recipient, 103 | address token, 104 | uint32[] calldata seasons, 105 | uint256[] calldata amounts 106 | ) external payable returns (uint256[] memory bdvs); 107 | 108 | function permitDeposits( 109 | address owner, 110 | address spender, 111 | address[] calldata tokens, 112 | uint256[] calldata values, 113 | uint256 deadline, 114 | uint8 v, 115 | bytes32 r, 116 | bytes32 s 117 | ) external payable; 118 | 119 | function permitDeposit( 120 | address owner, 121 | address spender, 122 | address token, 123 | uint256 value, 124 | uint256 deadline, 125 | uint8 v, 126 | bytes32 r, 127 | bytes32 s 128 | ) external payable; 129 | } 130 | -------------------------------------------------------------------------------- /test/integration/interfaces/IUniswap.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.20; 3 | 4 | interface IUniswapV2Router { 5 | function swapExactTokensForTokens( 6 | uint256 amountIn, 7 | uint256 amountOutMin, 8 | address[] calldata path, 9 | address to, 10 | uint256 deadline 11 | ) external returns (uint256[] memory amounts); 12 | 13 | function swapTokensForExactTokens( 14 | uint256 amountOut, 15 | uint256 amountInMax, 16 | address[] calldata path, 17 | address to, 18 | uint256 deadline 19 | ) external returns (uint256[] memory amounts); 20 | 21 | function addLiquidity( 22 | address tokenA, 23 | address tokenB, 24 | uint256 amountADesired, 25 | uint256 amountBDesired, 26 | uint256 amountAMin, 27 | uint256 amountBMin, 28 | address to, 29 | uint256 deadline 30 | ) external returns (uint256 amountA, uint256 amountB, 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 | 43 | interface IUniswapV3Router { 44 | struct ExactInputSingleParams { 45 | address tokenIn; 46 | address tokenOut; 47 | uint24 fee; 48 | address recipient; 49 | uint256 deadline; 50 | uint256 amountIn; 51 | uint256 amountOutMinimum; 52 | uint160 sqrtPriceLimitX96; 53 | } 54 | 55 | /// @notice Swaps amountIn of one token for as much as possible of another token 56 | /// @param params The parameters necessary for the swap, encoded as ExactInputSingleParams in calldata 57 | /// @return amountOut The amount of the received token 58 | function exactInputSingle(ExactInputSingleParams calldata params) external payable returns (uint256 amountOut); 59 | 60 | struct ExactInputParams { 61 | bytes path; 62 | address recipient; 63 | uint256 deadline; 64 | uint256 amountIn; 65 | uint256 amountOutMinimum; 66 | } 67 | 68 | /// @notice Swaps amountIn of one token for as much as possible of another along the specified path 69 | /// @param params The parameters necessary for the multi-hop swap, encoded as ExactInputParams in calldata 70 | /// @return amountOut The amount of the received token 71 | function exactInput(ExactInputParams calldata params) external payable returns (uint256 amountOut); 72 | } 73 | 74 | interface IUniswapV2Factory { 75 | function getPair(address token0, address token1) external view returns (address); 76 | } 77 | -------------------------------------------------------------------------------- /test/libraries/LibBytes16.t.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * SPDX-License-Identifier: MIT 3 | * 4 | */ 5 | pragma solidity ^0.8.20; 6 | 7 | import "test/TestHelper.sol"; 8 | 9 | import {LibBytes16} from "src/libraries/LibBytes16.sol"; 10 | 11 | contract LibBytes16Test is TestHelper { 12 | uint256 constant NUM_RESERVES_MAX = 8; 13 | bytes32 constant RESERVES_STORAGE_SLOT = bytes32(uint256(keccak256("reserves.storage.slot")) - 1); 14 | 15 | /// @dev Store fuzzed reserves, re-read and compare. 16 | function testFuzz_storeAndReadBytes16(uint256 n, bytes16[8] memory _reserves) public { 17 | n = bound(n, 0, NUM_RESERVES_MAX); 18 | 19 | // Use the first `n` reserves. Cast uint128 reserves -> uint256 20 | bytes16[] memory reserves = new bytes16[](n); 21 | for (uint256 i; i < n; i++) { 22 | reserves[i] = _reserves[i]; 23 | } 24 | LibBytes16.storeBytes16(RESERVES_STORAGE_SLOT, reserves); 25 | 26 | bytes32 slot = RESERVES_STORAGE_SLOT; 27 | bytes32 test; 28 | assembly { 29 | test := sload(slot) 30 | } 31 | console.logBytes32(test); 32 | assembly { 33 | test := sload(add(slot, 32)) 34 | } 35 | console.logBytes32(test); 36 | 37 | // Re-read reserves and compare 38 | bytes16[] memory reserves2 = LibBytes16.readBytes16(RESERVES_STORAGE_SLOT, n); 39 | for (uint256 i; i < reserves2.length; i++) { 40 | console.log(i); 41 | assertEq(reserves2[i], reserves[i], "ByteStorage: reserves mismatch"); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test/libraries/LibContractInfo.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.20; 3 | 4 | import {TestHelper, console} from "test/TestHelper.sol"; 5 | import {LibContractInfo} from "src/libraries/LibContractInfo.sol"; 6 | 7 | contract LibMathTest is TestHelper { 8 | using LibContractInfo for address; 9 | 10 | function setUp() public { 11 | setupWell(2); // setting up a well just to get some mock tokens 12 | } 13 | 14 | function test_getSymbol() public { 15 | assertEq(address(tokens[0]).getSymbol(), "TOKEN0"); 16 | } 17 | 18 | function test_getName() public { 19 | assertEq(address(tokens[0]).getName(), "Token 0"); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/libraries/LibLastReserveBytes.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.20; 3 | 4 | import "test/TestHelper.sol"; 5 | 6 | import {LibLastReserveBytes} from "src/libraries/LibLastReserveBytes.sol"; 7 | 8 | contract LibLastReserveBytesTest is TestHelper { 9 | using LibLastReserveBytes for bytes32; 10 | 11 | uint256 constant NUM_RESERVES_MAX = 8; 12 | bytes32 constant RESERVES_STORAGE_SLOT = bytes32(uint256(keccak256("reserves.storage.slot")) - 1); 13 | 14 | /// @dev Store fuzzed reserves, re-read and compare. 15 | function testEmaFuzz_storeAndRead( 16 | uint8 n, 17 | uint40 lastTimestamp, 18 | uint256[NUM_RESERVES_MAX] memory _reserves 19 | ) public { 20 | vm.assume(n <= NUM_RESERVES_MAX); 21 | 22 | // Use the first `n` reserves. Cast uint104 reserves -> uint256 23 | uint256[] memory reserves = new uint256[](n); 24 | for (uint256 i; i < n; i++) { 25 | reserves[i] = _reserves[i]; 26 | } 27 | RESERVES_STORAGE_SLOT.storeLastReserves(lastTimestamp, reserves); 28 | 29 | // Re-read reserves and compare 30 | (uint8 _n, uint40 _lastTimestamp, uint256[] memory reserves2) = RESERVES_STORAGE_SLOT.readLastReserves(); 31 | uint8 __n = RESERVES_STORAGE_SLOT.readNumberOfReserves(); 32 | assertEq(__n, n, "ByteStorage: n mismatch"); 33 | assertEq(_n, n, "ByteStorage: n mismatch"); 34 | assertEq(_lastTimestamp, lastTimestamp, "ByteStorage: lastTimestamp mismatch"); 35 | for (uint256 i; i < reserves2.length; i++) { 36 | assertApproxEqRelN(reserves2[i], reserves[i], 1); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /test/libraries/TestABDK.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.20; 3 | 4 | import "test/TestHelper.sol"; 5 | import "src/libraries/ABDKMathQuad.sol"; 6 | import "oz/utils/Strings.sol"; 7 | 8 | contract ABDKTest is TestHelper { 9 | using ABDKMathQuad for uint256; 10 | using ABDKMathQuad for bytes16; 11 | using Strings for uint256; 12 | 13 | int256 constant uUINT128 = 2 ** 128; 14 | 15 | //////////////////// CORE //////////////////// 16 | 17 | /** 18 | * @dev no hysteresis: 2^(log2(a)) == a +/- 1 (due to library rounding) 19 | */ 20 | function testFuzz_log2Pow2(uint256 a) public { 21 | a = bound(a, 1, type(uint256).max); 22 | uint256 b = (a.fromUInt().log_2()).pow_2().toUInt(); 23 | if (a <= 1e18) { 24 | assertApproxEqAbs(a, b, 1); 25 | } else { 26 | assertApproxEqRel(a, b, 1); 27 | } 28 | } 29 | 30 | //////////////////// EXTENSIONS //////////////////// 31 | 32 | function test_powu1() public { 33 | bytes16 pu = powuFraction(9, 10, 10); 34 | uint256 puu = uint256(pu.to128x128()); 35 | uint256 expected = 118_649_124_891_528_663_468_500_301_601_258_807_155; 36 | assertApproxEqRelN(puu, expected, 1, 32); 37 | } 38 | 39 | function testFuzz_powu(uint256 num, uint256 denom, uint256 exp) public { 40 | denom = bound(denom, 1, type(uint16).max); 41 | num = bound(num, 1, denom); 42 | 43 | string[] memory inputs = new string[](8); 44 | inputs[0] = "python"; 45 | inputs[1] = "test/differential/powu.py"; 46 | inputs[2] = "--numerator"; 47 | inputs[3] = uint256(num).toString(); 48 | inputs[4] = "--denominator"; 49 | inputs[5] = uint256(denom).toString(); 50 | inputs[6] = "--exponent"; 51 | inputs[7] = uint256(exp).toString(); 52 | bytes memory result = vm.ffi(inputs); 53 | 54 | bytes16 pu = powuFraction(num, denom, exp); 55 | uint256 puu = uint256(pu.to128x128()); 56 | uint256 pypu = uint256(abi.decode(result, (int256))); 57 | 58 | // Rounding error starts at 5e27 59 | if (puu > 5e27) { 60 | assertApproxEqRelN(puu, pypu, 2, 28); // expecting precision to 2e-28 61 | } else { 62 | assertApproxEqAbs(puu, pypu, 1); 63 | } 64 | } 65 | 66 | /// @dev calculate (a/b)^c 67 | function powuFraction(uint256 a, uint256 b, uint256 c) public pure returns (bytes16) { 68 | return a.fromUInt().div(b.fromUInt()).powu(c); 69 | } 70 | 71 | function testFuzz_FromUIntToLog2(uint256 x) public pure { 72 | x = bound(x, 1, type(uint256).max); 73 | assertEq(ABDKMathQuad.fromUInt(x).log_2(), ABDKMathQuad.fromUIntToLog2(x)); 74 | } 75 | 76 | function testFuzz_pow_2ToUInt(uint256 x) public pure { 77 | x = bound(x, 0, 255); 78 | 79 | // test the pow_2ToUInt function 80 | bytes16 _x = x.fromUInt(); 81 | assertEq(ABDKMathQuad.pow_2(_x).toUInt(), ABDKMathQuad.pow_2ToUInt(_x)); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /test/pumps/Pump.Helpers.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.20; 3 | 4 | import {MultiFlowPump, ABDKMathQuad} from "src/pumps/MultiFlowPump.sol"; 5 | import {simCapReserve50Percent, from18, to18} from "test/pumps/PumpHelpers.sol"; 6 | import {log2, powu, UD60x18, wrap, unwrap} from "prb/math/UD60x18.sol"; 7 | import {console, TestHelper} from "test/TestHelper.sol"; 8 | 9 | contract PumpHelpersTest is TestHelper, MultiFlowPump { 10 | uint256[5] testCasesInput = [1, 2, 3, 4, 5]; 11 | uint256[5] testCasesOutput = [1, 1, 2, 2, 3]; 12 | 13 | constructor() MultiFlowPump() {} 14 | 15 | function test_getSlotForAddress() public { 16 | address addr = address(0xa755A670Aaf1FeCeF2bea56115E65e03F7722A79); 17 | bytes32 bytesAddress = _getSlotForAddress(addr); 18 | 19 | assertEq(bytesAddress, 0xa755a670aaf1fecef2bea56115e65e03f7722a79000000000000000000000000); 20 | } 21 | 22 | function test_getDeltaTimeStamp() public { 23 | vm.warp(200); 24 | uint40 providedBlock = 100; 25 | uint40 expectedDelta = 100; 26 | 27 | uint256 delta = _getDeltaTimestamp(providedBlock); 28 | 29 | assertEq(delta, expectedDelta); 30 | } 31 | 32 | function test_getSlotOffset() public { 33 | for (uint256 i; i < testCasesInput.length; i++) { 34 | assertEq(_getSlotsOffset(testCasesInput[i]), testCasesOutput[i]); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /test/pumps/Pump.Longevity.t.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * SPDX-License-Identifier: MIT 3 | * 4 | */ 5 | pragma solidity ^0.8.20; 6 | 7 | import {console, TestHelper} from "test/TestHelper.sol"; 8 | import {ABDKMathQuad, MultiFlowPump} from "src/pumps/MultiFlowPump.sol"; 9 | import {MockReserveWell} from "mocks/wells/MockReserveWell.sol"; 10 | import {mockPumpData} from "test/pumps/PumpHelpers.sol"; 11 | import {ConstantProduct2} from "src/functions/ConstantProduct2.sol"; 12 | 13 | import {generateRandomUpdate, from18, to18} from "test/pumps/PumpHelpers.sol"; 14 | import {log2, powu, UD60x18, wrap, unwrap} from "prb/math/UD60x18.sol"; 15 | import {exp2, log2, powu, UD60x18, wrap, unwrap, uUNIT} from "prb/math/UD60x18.sol"; 16 | 17 | contract PumpLongevityTest is TestHelper { 18 | using ABDKMathQuad for bytes16; 19 | using ABDKMathQuad for uint256; 20 | 21 | MultiFlowPump pump; 22 | MockReserveWell mWell; 23 | bytes data; 24 | uint256[] b = new uint256[](2); 25 | 26 | constructor() {} 27 | 28 | function setUp() public { 29 | mWell = new MockReserveWell(); 30 | initUser(); 31 | pump = new MultiFlowPump(); 32 | data = mockPumpData(); 33 | wellFunction.target = address(new ConstantProduct2()); 34 | mWell.setWellFunction(wellFunction); 35 | } 36 | 37 | function testIterate() public prank(user) { 38 | bytes32 seed = bytes32(0); 39 | uint256 n = 2; 40 | uint256[] memory balances; 41 | uint40 timeStep; 42 | uint256 timestamp = block.timestamp; 43 | for (uint256 i; i < 4000; ++i) { 44 | if (i % 1000 == 0) { 45 | console.log(i); 46 | } 47 | (balances, timeStep, seed) = generateRandomUpdate(n, seed); 48 | // console.log("Time Step: ", timeStep); 49 | // for (uint256 j; j < n; ++j) { 50 | // console.log("Balance", j, balances[j]); 51 | // } 52 | increaseTime(timeStep); 53 | mWell.update(address(pump), balances, data); 54 | } 55 | 56 | // uint256[] memory lastReserves = pump.readLastReserves(address(mWell)); 57 | // uint256[] memory currentReserves = pump.readInstantaneousReserves(address(mWell)); 58 | // bytes16[] memory lastCumulativeReserves = pump.readLastCumulativeReserves(address(mWell)); 59 | 60 | // for (uint256 i; i < n; ++i) { 61 | // console.log("Reserve", i, balances[i]); 62 | // console.log("Last Reserve", i, lastReserves[i]); 63 | // console.log("Current Reserve", i, currentReserves[i]); 64 | // console.logBytes16(lastCumulativeReserves[i]); 65 | // } 66 | uint256 deltaTimestamp = block.timestamp - timestamp; 67 | console.log("Time passed:", deltaTimestamp / 60 / 60 / 24 / 365, "years"); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /test/pumps/Pump.NotInitialized.t.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * SPDX-License-Identifier: MIT 3 | * 4 | */ 5 | pragma solidity ^0.8.20; 6 | 7 | import {console, TestHelper} from "test/TestHelper.sol"; 8 | import {MultiFlowPump} from "src/pumps/MultiFlowPump.sol"; 9 | import {mockPumpData} from "test/pumps/PumpHelpers.sol"; 10 | import {MockReserveWell} from "mocks/wells/MockReserveWell.sol"; 11 | import {IMultiFlowPumpErrors} from "src/interfaces/pumps/IMultiFlowPumpErrors.sol"; 12 | import {from18} from "test/pumps/PumpHelpers.sol"; 13 | 14 | contract PumpNotInitialized is TestHelper { 15 | MultiFlowPump pump; 16 | bytes data; 17 | MockReserveWell mWell; 18 | uint256[] b = new uint256[](2); 19 | 20 | function setUp() public { 21 | mWell = new MockReserveWell(); 22 | initUser(); 23 | pump = new MultiFlowPump(); 24 | uint256[] memory reserves = new uint256[](2); 25 | mWell.setReserves(reserves); 26 | data = mockPumpData(); 27 | } 28 | 29 | function test_not_initialized_last_cumulative_reserves() public { 30 | vm.expectRevert(IMultiFlowPumpErrors.NotInitialized.selector); 31 | pump.readLastCumulativeReserves(address(mWell), data); 32 | } 33 | 34 | function test_not_initialized_cumulative_reserves() public { 35 | vm.expectRevert(IMultiFlowPumpErrors.NotInitialized.selector); 36 | pump.readCumulativeReserves(address(mWell), data); 37 | } 38 | 39 | function test_not_initialized_last_instantaneous_reserves() public { 40 | vm.expectRevert(IMultiFlowPumpErrors.NotInitialized.selector); 41 | pump.readLastInstantaneousReserves(address(mWell), data); 42 | } 43 | 44 | function test_not_initialized_instantaneous_reserves() public { 45 | vm.expectRevert(IMultiFlowPumpErrors.NotInitialized.selector); 46 | pump.readInstantaneousReserves(address(mWell), data); 47 | } 48 | 49 | function test_not_initialized_last_capped_reserves() public { 50 | vm.expectRevert(IMultiFlowPumpErrors.NotInitialized.selector); 51 | pump.readLastCappedReserves(address(mWell), data); 52 | } 53 | 54 | function test_not_initialized_capped_reserves() public { 55 | vm.expectRevert(IMultiFlowPumpErrors.NotInitialized.selector); 56 | pump.readCappedReserves(address(mWell), data); 57 | } 58 | 59 | function test_not_initialized_twa_reserves() public { 60 | vm.expectRevert(IMultiFlowPumpErrors.NotInitialized.selector); 61 | pump.readTwaReserves(address(mWell), data, 0, data); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /test/pumps/Pump.TimeWeightedAverage.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.20; 3 | 4 | import {console, TestHelper} from "test/TestHelper.sol"; 5 | import {MultiFlowPump, ABDKMathQuad} from "src/pumps/MultiFlowPump.sol"; 6 | import {mockPumpData, from18, to18} from "test/pumps/PumpHelpers.sol"; 7 | import {MockReserveWell} from "mocks/wells/MockReserveWell.sol"; 8 | import {ConstantProduct2} from "src/functions/ConstantProduct2.sol"; 9 | 10 | import {log2, powu, UD60x18, wrap, unwrap} from "prb/math/UD60x18.sol"; 11 | import {exp2, log2, powu, UD60x18, wrap, unwrap, uUNIT} from "prb/math/UD60x18.sol"; 12 | 13 | contract PumpTimeWeightedAverageTest is TestHelper { 14 | using ABDKMathQuad for bytes16; 15 | 16 | MultiFlowPump pump; 17 | bytes data; 18 | MockReserveWell mWell; 19 | uint256[] b = new uint256[](2); 20 | 21 | uint256 constant CAP_INTERVAL = 12; 22 | 23 | /// @dev for this test, `user` = a Well that's calling the Pump 24 | function setUp() public { 25 | mWell = new MockReserveWell(); 26 | initUser(); 27 | pump = new MultiFlowPump(); 28 | data = mockPumpData(); 29 | wellFunction.target = address(new ConstantProduct2()); 30 | mWell.setWellFunction(wellFunction); 31 | 32 | // Send first update to the Pump, which will initialize it 33 | vm.prank(user); 34 | b[0] = 1e6; 35 | b[1] = 2e6; 36 | mWell.update(address(pump), b, data); 37 | mWell.update(address(pump), b, data); 38 | 39 | uint256[] memory checkReserves = mWell.getReserves(); 40 | assertEq(checkReserves[0], b[0]); 41 | assertEq(checkReserves[1], b[1]); 42 | } 43 | 44 | function testTWAReserves() public prank(user) { 45 | increaseTime(12); 46 | 47 | bytes memory startCumulativeReserves = pump.readCumulativeReserves(address(mWell), data); 48 | uint256[] memory lastReserves = pump.readLastCappedReserves(address(mWell), data); 49 | 50 | assertApproxEqAbs(lastReserves[0], 1e6, 1); 51 | assertApproxEqAbs(lastReserves[1], 2e6, 1); 52 | 53 | increaseTime(120); 54 | uint256[] memory twaReserves; 55 | 56 | (twaReserves,) = pump.readTwaReserves(address(mWell), startCumulativeReserves, block.timestamp - 120, data); 57 | 58 | assertApproxEqAbs(twaReserves[0], 1e6, 1); 59 | assertApproxEqAbs(twaReserves[1], 2e6, 1); 60 | 61 | b[0] = 2e6; 62 | b[1] = 4e6; 63 | mWell.update(address(pump), b, data); 64 | 65 | increaseTime(120); 66 | 67 | (twaReserves,) = pump.readTwaReserves(address(mWell), startCumulativeReserves, block.timestamp - 240, data); 68 | 69 | assertEq(twaReserves[0], 1_414_213); // Geometric Mean of 1e6 and 2e6 is 1_414_213 70 | assertEq(twaReserves[1], 2_828_427); // Geometric mean of 2e6 and 4e6 is 2_828_427 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /test/pumps/PumpHelpers.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Unlicense 2 | pragma solidity >=0.8.0; 3 | 4 | import {ABDKMathQuad} from "src/libraries/ABDKMathQuad.sol"; 5 | import {console} from "forge-std/Test.sol"; 6 | import {MultiFlowPump} from "src/pumps/MultiFlowPump.sol"; 7 | 8 | uint256 constant MAX_128 = 2 ** 128; 9 | uint256 constant MAX_E18 = 1e18; 10 | 11 | function from18(uint256 a) pure returns (bytes16 result) { 12 | return ABDKMathQuad.from128x128(int256((a * MAX_128) / MAX_E18)); 13 | } 14 | 15 | function to18(bytes16 a) pure returns (uint256 result) { 16 | return (uint256(ABDKMathQuad.to128x128(a)) * MAX_E18) / MAX_128; 17 | } 18 | 19 | function simCapReserve50Percent( 20 | uint256 lastReserve, 21 | uint256 reserve, 22 | uint256 blocks 23 | ) view returns (uint256 cappedReserve) { 24 | uint256 limitReserve = lastReserve * 1e18; 25 | 26 | uint256 multiplier = lastReserve < reserve ? 1.5e6 : 0.5e6; 27 | 28 | uint256 tempReserve; 29 | for (uint256 i; i < blocks; ++i) { 30 | unchecked { 31 | tempReserve = (limitReserve * multiplier) / 1e6; 32 | } 33 | if (lastReserve < reserve && tempReserve < limitReserve) { 34 | limitReserve = type(uint256).max; 35 | break; 36 | } 37 | limitReserve = tempReserve; 38 | } 39 | limitReserve = limitReserve / 1e18; 40 | 41 | console.log("limitReserve", limitReserve); 42 | console.log("lastReserve", lastReserve); 43 | console.log("reserve", reserve); 44 | 45 | cappedReserve = (lastReserve < reserve && limitReserve < reserve) 46 | || (lastReserve > reserve && limitReserve > reserve) ? limitReserve : reserve; 47 | } 48 | 49 | function generateRandomUpdate( 50 | uint256 n, 51 | bytes32 seed 52 | ) pure returns (uint256[] memory balances, uint40 timeIncrease, bytes32 newSeed) { 53 | balances = new uint256[](n); 54 | seed = stepSeed(seed); 55 | timeIncrease = uint40(uint256(seed)) % 50_000_000; 56 | for (uint256 i; i < n; ++i) { 57 | seed = stepSeed(seed); 58 | balances[i] = uint256(seed) % 1e32; // 59 | } 60 | newSeed = seed; 61 | } 62 | 63 | function stepSeed(bytes32 seed) pure returns (bytes32 newSeed) { 64 | newSeed = keccak256(abi.encode(seed)); 65 | } 66 | 67 | function encodePumpData( 68 | bytes16 alpha, 69 | uint256 capInterval, 70 | MultiFlowPump.CapReservesParameters memory crp 71 | ) pure returns (bytes memory data) { 72 | data = abi.encode(alpha, capInterval, crp); 73 | } 74 | 75 | function mockPumpData() pure returns (bytes memory data) { 76 | bytes16[][] memory maxRateChanges = new bytes16[][](2); 77 | maxRateChanges[0] = new bytes16[](2); 78 | maxRateChanges[1] = new bytes16[](2); 79 | maxRateChanges[0][1] = from18(0.5e18); 80 | maxRateChanges[1][0] = from18(0.5e18); 81 | 82 | data = encodePumpData( 83 | from18(0.9e18), 12, MultiFlowPump.CapReservesParameters(maxRateChanges, from18(0.5e18), from18(0.4761904762e18)) 84 | ); 85 | } 86 | -------------------------------------------------------------------------------- /test/pumps/simulate.py: -------------------------------------------------------------------------------- 1 | import os 2 | import argparse 3 | from eth_abi import decode 4 | from decimal import * 5 | import pandas as pd 6 | import numpy as np 7 | 8 | def main(args): 9 | val = decode(['(uint256,uint256,uint256,uint256)[]'], bytes.fromhex(args.data[2:])) 10 | arr = np.asarray(val[0]) 11 | pd.DataFrame( 12 | arr, 13 | columns=['j', 'prev', 'curr', 'capped'] 14 | ).to_csv( 15 | os.path.join("test", "output", f"{args.name}.csv"), 16 | index=False 17 | ) 18 | print("Done") 19 | 20 | def parse_args(): 21 | parser = argparse.ArgumentParser() 22 | parser.add_argument("--data", "-d", type=str) 23 | parser.add_argument("--name", "-n", type=str) 24 | return parser.parse_args() 25 | 26 | if __name__ == '__main__': 27 | args = parse_args() 28 | main(args) -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | husky@8.0.3: 6 | version "8.0.3" 7 | resolved "https://registry.yarnpkg.com/husky/-/husky-8.0.3.tgz#4936d7212e46d1dea28fef29bb3a108872cd9184" 8 | integrity sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg== 9 | --------------------------------------------------------------------------------