├── .gitattributes ├── .github ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── pull-request.yml │ └── push-to-main.yml ├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── audits ├── 2021-05-03-Trail_of_Bits.pdf ├── 2022-09-DFXv2-Pickax-Audit.pdf ├── 2022-09-DFXv2-Sebone-Audit.pdf └── 2023-02-07-DFXv2-Kalos-Audit.pdf ├── foundry.toml ├── remappings.txt ├── script ├── Addresses.sol ├── ArbitrumDeployment.s.sol ├── ArbitrumGYENCADC.s.sol ├── ArbitrumRouterZap.s.sol ├── CurveParams.sol ├── MainnetDeployment.s.sol ├── MainnetGBPT.s.sol └── PolygonDeployment.s.sol ├── src ├── AssimilatorFactory.sol ├── Assimilators.sol ├── Config.sol ├── Curve.sol ├── CurveFactory.sol ├── CurveFactoryV2.sol ├── CurveMath.sol ├── Orchestrator.sol ├── ProportionalLiquidity.sol ├── Router.sol ├── Storage.sol ├── Structs.sol ├── Swaps.sol ├── ViewLiquidity.sol ├── Zap.sol ├── assimilators │ └── AssimilatorV2.sol ├── interfaces │ ├── IAssimilator.sol │ ├── IAssimilatorFactory.sol │ ├── IConfig.sol │ ├── ICurve.sol │ ├── ICurveFactory.sol │ ├── IERC20Detailed.sol │ ├── IFlashCallback.sol │ ├── IFreeFromUpTo.sol │ └── IOracle.sol └── lib │ ├── ABDKMath64x64.sol │ ├── ABDKMathQuad.sol │ ├── FullMath.sol │ ├── NoDelegateCall.sol │ └── UnsafeMath64x64.sol └── test ├── Curve.t.sol ├── CurveFactoryV2.t.sol ├── Flashloan.t.sol ├── Haechi ├── 12.sol ├── 13.sol ├── 14.sol ├── 6.sol └── 8.sol ├── Router.t.sol ├── V2.t.sol ├── Zap.t.sol ├── lib ├── Address.sol ├── CheatCodes.sol ├── CurveParams.sol ├── LowGasSafeMath.sol ├── MockAggregator.sol ├── MockChainlinkOracle.sol ├── MockOracleFactory.sol ├── MockToken.sol └── MockUser.sol └── utils ├── CurveFlash.sol ├── CurveFlashReentrancy.sol ├── FlashStructs.sol └── Utils.sol /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | # Integrations Checklist 4 | 5 | - [ ] Have any function signatures changed? If yes, outline below. 6 | - [ ] Have any features changed or been added? If yes, outline below. 7 | - [ ] Have any events changed or been added? If yes, outline below. 8 | - [ ] Has all documentation been updated? 9 | 10 | # Changelog 11 | 12 | ## Function Signature Changes 13 | 14 | ## Features 15 | 16 | ## Events -------------------------------------------------------------------------------- /.github/workflows/pull-request.yml: -------------------------------------------------------------------------------- 1 | name: Basic PR Tests 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | run-ci: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | 11 | - name: Install Foundry 12 | uses: foundry-rs/foundry-toolchain@v1 13 | with: 14 | version: nightly 15 | 16 | - name: Install submodules 17 | run: | 18 | git config --global url."https://github.com/".insteadOf "git@github.com:" 19 | git submodule update --init --recursive 20 | 21 | - name: Run forge tests 22 | # run: ./test.sh -p super_deep 23 | run: | 24 | forge clean 25 | forge test --optimize --optimizer-runs 1000000 -v -f https://eth-mainnet.g.alchemy.com/v2/${{ secrets.ALCHEMY_API_KEY }} --fork-block-number 15129966 -------------------------------------------------------------------------------- /.github/workflows/push-to-main.yml: -------------------------------------------------------------------------------- 1 | name: 50k fuzz run test on push to main 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | run-ci: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | 14 | - name: Install Foundry 15 | uses: foundry-rs/foundry-toolchain@v1 16 | with: 17 | version: nightly 18 | 19 | - name: Install submodules 20 | run: | 21 | git config --global url."https://github.com/".insteadOf "git@github.com:" 22 | git submodule update --init --recursive 23 | 24 | - name: Run forge tests 25 | # run: ./test.sh -p super_deep 26 | run: | 27 | forge clean 28 | forge test --optimize --optimizer-runs 1000000 -v -f https://eth-mainnet.g.alchemy.com/v2/${{ secrets.ALCHEMY_API_KEY }} --fork-block-number 15129966 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | cache/ 2 | out/ 3 | broadcast/ 4 | node_modules/ 5 | .env -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/forge-std"] 2 | path = lib/forge-std 3 | url = https://github.com/brockelmore/forge-std 4 | [submodule "lib/openzeppelin-contracts"] 5 | path = lib/openzeppelin-contracts 6 | url = https://github.com/OpenZeppelin/openzeppelin-contracts 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 DFX Finance 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DFX Protocol V2 2 | 3 | A decentralized foreign exchange protocol optimized for stablecoins. 4 | 5 | [![Discord](https://img.shields.io/discord/786747729376051211.svg?color=768AD4&label=discord&logo=https%3A%2F%2Fdiscordapp.com%2Fassets%2F8c9701b98ad4372b58f13fd9f65f966e.svg)](http://discord.dfx.finance/) 6 | [![Twitter Follow](https://img.shields.io/twitter/follow/DFXFinance.svg?label=DFXFinance&style=social)](https://twitter.com/DFXFinance) 7 | 8 | ## Overview 9 | 10 | DFX v2 is an update from DFX protocol v0.5 with some additional features including the protocol fee, which is set by the percentage of the platform fee(which incurs for each swap on all the pools across the platform), fixing issues of invariant check, and the support of flashloans. The major change from the previous version is, V2 is more generalized for users, meaning anybody can create their curves(pools) while V0.5 only allowed the DFX team to create pools. 11 | 12 | There are two major parts to the protocol: **Assimilators** and **Curves**. Assimilators allow the AMM to handle pairs of different value while also retrieving reported oracle prices for respective currencies. Curves allow the custom parameterization of the bonding curve with dynamic fees, halting bounderies, etc. 13 | 14 | ### Assimilators 15 | 16 | Assimilators are a key part of the protocol, it converts all amounts to a "numeraire" which is essentially a base value used for computations across the entire protocol. This is necessary as we are dealing with pairs of different values. **AssimilatorFactory** is responsible for deploying new AssimilatorV2. 17 | 18 | Oracle price feeds are also piped in through the assimilator as they inform what numeraire amounts should be set. Since oracle price feeds report their values in USD, all assimilators attempt to convert token values to a numeraire amount based on USD. 19 | 20 | ### Curve Parameter Terminology 21 | 22 | High level overview. 23 | 24 | | Name | Description | 25 | | --------- | --------------------------------------------------------------------------------------------------------- | 26 | | Weights | Weighting of the pair (only 50/50) | 27 | | Alpha | The maximum and minimum allocation for each reserve | 28 | | Beta | Liquidity depth of the exchange; The higher the value, the flatter the curve at the reported oracle price | 29 | | Delta/Max | Slippage when exchange is not at the reported oracle price | 30 | | Epsilon | Fixed fee | 31 | | Lambda | Dynamic fee captured when slippage occurs | 32 | 33 | In order to prevent anti-slippage being greater than slippate, DFX V2 requires deployers to set Lambda to 1(1e18). 34 | 35 | For a more in-depth discussion, refer to [section 3 of the shellprotocol whitepaper](https://github.com/cowri/shell-solidity-v1/blob/master/Shell_White_Paper_v1.0.pdf) 36 | 37 | ### Major changes from the Shell Protocol 38 | 39 | The main changes between V2 and the original code can be found in the following files: 40 | 41 | - All the assimilators 42 | - `AssimilatorV2.sol` 43 | - `CurveFactoryV2.sol` 44 | - `CurveMath.sol` 45 | - `ProportionalLiquidity.sol` 46 | - `Swaps.sol` 47 | - `Structs.sol` 48 | 49 | #### Different Valued Pairs 50 | 51 | In the original implementation, all pools are assumed to be baskets of like-valued tokens. In our implementation, all pools are assumed to be pairs of different-valued FX stablecoins (of which one side is always USDC). 52 | 53 | This is achieved by having custom assimilators that normalize the foreign currencies to their USD counterparts. We're sourcing our FX price feed from chainlink oracles. See above for more information about assimilators. 54 | 55 | Withdrawing and depositing related operations will respect the existing LP ratio. As long as the pool ratio hasn't changed since the deposit, amount in ~= amount out (minus fees), even if the reported price on the oracle changes. The oracle is only here to assist with efficient swaps. 56 | 57 | #### Flashloans 58 | Flashloans live in every curve contract produced by the CurveFactory. DFX curve flash function is based on the the flashloan model in UniswapV2. The user calling flash function must conform to the `IFlash.sol` interface. It must containing their own logic along with code to return the correct amount of tokens requested along with its respective fee. Flash function will check for the balances of the curve before and after to ensure that the correct amount of fees has been sent to the treasury as well as funds returned back to the curve. 59 | 60 | ## Third Party Libraries 61 | 62 | - [Openzeppelin contracts (v3.3.0)](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v3.3.0) 63 | - [ABDKMath (v2.4)](https://github.com/abdk-consulting/abdk-libraries-solidity/releases/tag/v2.4) 64 | - [Shell Protocol@48dac1c](https://github.com/cowri/shell-solidity-v1/tree/48dac1c1a18e2da292b0468577b9e6cbdb3786a4) 65 | 66 | 67 | ## Test Locally 68 | 1. Install Foundry 69 | 70 | - [Foundry Docs](https://jamesbachini.com/foundry-tutorial/) 71 | 72 | 2. Download all dependencies. 73 | ``` 74 | forge install 75 | ``` 76 | 2. Run Ethereum mainnet forked testnet on your local in one terminal: 77 | 78 | ``` 79 | anvil -f https://mainnet.infura.io/v3/ --fork-block-number 15129966 80 | ``` 81 | 82 | 3. In another terminal, run V2 test script: 83 | 84 | ``` 85 | forge test --match-contract V2Test -vvv -f http://127.0.0.1:8545 86 | ``` 87 | 88 | 4. Run Protocol Fee test: 89 | 90 | ``` 91 | forge test --match-contract ProtocolFeeTest -vvv -f http://127.0.0.1:8545 92 | ``` 93 | 94 | ## Test Cases 95 | ### ```V2.t.sol``` 96 | 1. testDeployTokenAndSwap 97 | 98 | - deploy a random erc20 token (we call it `gold` token) and it's price oralce (`gold oracle`), gold : usdc ratio is set to 1:20 in the test 99 | - deploy a new curve from Curve Factory 100 | - try swaps back and forth between `gold` and `usdc` 101 | - in each swap, usdc and gold swap in and out amount are correct based on the gold oracle's price 102 | 103 | 2. testForeignStableCoinSwap 104 | 105 | - test uses the EURC and CADC tokens deployed on the ethereum mainnet 106 | - this test is to ensure deploying curves by curveFactoryV2 doesn't break any stable swap features from the previous version 107 | 108 | 3. testTotalSupply 109 | 110 | - this test has nothing to do with V2 update 111 | - in the test, we directly transfer erc20 tokens to the curve without calling deposit function 112 | - the test ensures total supply of curve lpt remains unchanged when tokens are transferred directly to the curve 113 | 4. testSwapDifference 114 | 115 | - this test is to ensure there is no anti-slippage occurred 116 | - We frist swap relatively large amount of token(saying from token A to token B) to change the pool ratio 117 | - we swap back all output amount of B to A 118 | - this test ensures the user gets all of his original A amount except the fee 119 | 5. testInvariant 120 | 121 | - this test ensures anybody can deposit any amount of LPs to the curve 122 | ### ```ProtocolFee.t.sol``` 123 | 1. testProtocolFeeUsdcCadcSwap 124 | 125 | - For each trade on DFX, platform fee is applied, it is set by Epsilon when deploying the curve 126 | - platform fee is splitted into 2 parts, some of the fee is sent back to the pool, while rest amount is sent to the protocol's treasury address (we call this `protocol fee`) 127 | - `protocol fee` is calculated by the following formular 128 | ``` 129 | protocol fee = platform fee * CurveFactoryV2.protocolFee / 100000 130 | ``` 131 | if protocolFee is set to 50,000 (50% because of 6 decimal places), then the platform fee is divided evenly to the treasury & curve 132 | ### ```Router.t.sol``` 133 | 1. testTargetSwap 134 | - Swaps a dynamic origin amount for a fixed target amount 135 | - this test checks if the swapped amount is correct within 99% accuracy by multiplying the requested amount at the foriegn exchange rate pulled from chainlink oracles 136 | - the test is repeated for trading unlike pairs to test their varying decimal places such as CADC -> XSGD, CADC -> EUROC, CADC -> USDC etc. as well as run through extensive fuzzing 137 | 138 | 2. testOriginSwap 139 | - Swaps a fixed origin amount for a dynamic target amount 140 | - this test checks if the swapped amount is correct within 99% accuracy by multiplying the requested amount at the foriegn exchange rate pulled from chainlink oracles 141 | - the test is repeated for trading unlike pairs to test their varying decimal places such as CADC -> XSGD, CADC -> EUROC, CADC -> USDC etc. as well as run through extensive fuzzing 142 | 143 | ### ```Flashloan.t.sol``` 144 | 1. testFlashloan 145 | - this test ensures the correct amount of stablecoins requested to be flashloaned from any DFX curves is correctly transfered to the contract calling the `flash` function 146 | - it also checks the correct amount of fees owed proportional to the epsilon value within the curve mutiplied by the amount borrowed is sent directly to the treasury 147 | - last sanity check is to check that the balance of USDC and the respective foreign stablecoin in the pool is equal or greater than before the flashloan was conducted 148 | - the test is repeated for already exisiting stablecoins including CADC, EUROC, XSGD to test their varying decimal places as well as run through extensive fuzzing 149 | 150 | 2. testFail_FlashloanFee 151 | - this tests fails upon a user calling the flash function and does not have enough funds to pay back the original loan including fees 152 | - the test is repeated for already exisiting stablecoins including CADC, EUROC, XSGD to test their varying decimal places as well as run through extensive fuzzing 153 | 154 | 3. testFail_FlashloanCurveDepth 155 | - this tests fails upon a user calling the flash function and the curve not having enough funds to lend to the user 156 | - the test is repeated for already exisiting stablecoins including CADC, EUROC, XSGD to test their varying decimal places as well as run through extensive fuzzing 157 | 158 | ### ```CurveFactoryV2.t.sol``` 159 | 1. testFailDuplicatePairs 160 | - this test ensures already exisiting pairs cannot be added to avoid duplicates 161 | 162 | 2. testNewPairs 163 | - this test ensures that new pairs can be properly added as long as they dont already exist 164 | 165 | 3. testUpdateFee 166 | - this test ensures protocol fee can be updated properly as long as it is within the correct range of 0 to 100% in 6 decimals 167 | 168 | 4. testFailUpdateFee 169 | - this test ensures trying to update the protocol fee to a new fee higher than 100% will revert in 6 decimals 170 | 171 | 5. testUpdateTreasury 172 | - this test ensures the protocol treasury address can be updated properly to a new address 173 | -------------------------------------------------------------------------------- /audits/2021-05-03-Trail_of_Bits.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfx-finance/protocol-v2/9cc8deee49515d1343671de26145ab40bc7e4490/audits/2021-05-03-Trail_of_Bits.pdf -------------------------------------------------------------------------------- /audits/2022-09-DFXv2-Pickax-Audit.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfx-finance/protocol-v2/9cc8deee49515d1343671de26145ab40bc7e4490/audits/2022-09-DFXv2-Pickax-Audit.pdf -------------------------------------------------------------------------------- /audits/2022-09-DFXv2-Sebone-Audit.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfx-finance/protocol-v2/9cc8deee49515d1343671de26145ab40bc7e4490/audits/2022-09-DFXv2-Sebone-Audit.pdf -------------------------------------------------------------------------------- /audits/2023-02-07-DFXv2-Kalos-Audit.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfx-finance/protocol-v2/9cc8deee49515d1343671de26145ab40bc7e4490/audits/2023-02-07-DFXv2-Kalos-Audit.pdf -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = 'src' 3 | out = 'out' 4 | libs = ['lib'] 5 | 6 | # See more config options https://github.com/foundry-rs/foundry/tree/master/config 7 | [rpc_endpoints] 8 | # mainnet = "${ETHEREUM_RPC_URL}" 9 | arbitrum = "${ARBITRUM_RPC_URL}" 10 | 11 | [etherscan] 12 | mainnet = { key = "${ETHERSCAN_API_KEY}" } 13 | # arbitrum = { key = "${ARBITRUM_API_KEY}" } 14 | -------------------------------------------------------------------------------- /remappings.txt: -------------------------------------------------------------------------------- 1 | @openzeppelin/=lib/openzeppelin-contracts/ 2 | @forge-std/=lib/forge-std/src/ -------------------------------------------------------------------------------- /script/Addresses.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.10; 3 | 4 | library Polygon { 5 | address public constant MULTISIG = 0x80D27bfb638F4Fea1e862f1bd07DEa577CB77D38; 6 | 7 | // Tokens 8 | address public constant USDC = 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174; 9 | address public constant CADC = 0x9de41aFF9f55219D5bf4359F167d1D0c772A396D; 10 | address public constant EURS = 0xE111178A87A3BFf0c8d18DECBa5798827539Ae99; 11 | address public constant XSGD = 0xDC3326e71D45186F113a2F448984CA0e8D201995; 12 | address public constant NZDS = 0xeaFE31Cd9e8E01C8f0073A2C974f728Fb80e9DcE; 13 | address public constant TRYB = 0x4Fb71290Ac171E1d144F7221D882BECAc7196EB5; 14 | 15 | // Token Decimals 16 | uint256 public constant USDC_DECIMALS = 6; 17 | uint256 public constant CADC_DECIMALS = 18; 18 | uint256 public constant EURS_DECIMALS = 2; 19 | uint256 public constant XSGD_DECIMALS = 6; 20 | uint256 public constant NZDS_DECIMALS = 6; 21 | uint256 public constant TRYB_DECIMALS = 6; 22 | 23 | // Oracles 24 | address public constant CHAINLINK_USDC_USD = 0xfE4A8cc5b5B2366C1B58Bea3858e81843581b2F7; 25 | address public constant CHAINLINK_CAD_USD = 0xACA44ABb8B04D07D883202F99FA5E3c53ed57Fb5; 26 | address public constant CHAINLINK_EUR_USD = 0x73366Fe0AA0Ded304479862808e02506FE556a98; 27 | address public constant CHAINLINK_SGD_USD = 0x8CE3cAc0E6635ce04783709ca3CC4F5fc5304299; 28 | address public constant CHAINLINK_NZD_USD = 0xa302a0B8a499fD0f00449df0a490DedE21105955; 29 | address public constant CHAINLINK_TRY_USD = 0xd78325DcA0F90F0FFe53cCeA1B02Bb12E1bf8FdB; 30 | 31 | // Epsilon (Pool Fee) 32 | uint256 public constant CADC_EPSILON = 5e14; // (0.05%) 33 | uint256 public constant EURS_EPSILON = 5e14; // (0.05%) 34 | uint256 public constant XSGD_EPSILON = 1e15; // (0.10%) 35 | uint256 public constant NZDS_EPSILON = 3e15; // (0.30%) 36 | uint256 public constant TRYB_EPSILON = 1e15; // (0.10%) 37 | } 38 | 39 | library Mainnet { 40 | address public constant MULTISIG = 0x27E843260c71443b4CC8cB6bF226C3f77b9695AF; 41 | address public constant CURVE_FACTORY = 0x9AdEAc3b6d29D9D5e543B8579e803a7ccE72C9cd; 42 | 43 | // Tokens 44 | address public constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; 45 | address public constant CADC = 0xcaDC0acd4B445166f12d2C07EAc6E2544FbE2Eef; 46 | address public constant EUROC = 0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c; 47 | address public constant XSGD = 0x70e8dE73cE538DA2bEEd35d14187F6959a8ecA96; 48 | address public constant NZDS = 0xDa446fAd08277B4D2591536F204E018f32B6831c; 49 | address public constant TRYB = 0x2C537E5624e4af88A7ae4060C022609376C8D0EB; 50 | address public constant GYEN = 0xC08512927D12348F6620a698105e1BAac6EcD911; 51 | address public constant XIDR = 0xebF2096E01455108bAdCbAF86cE30b6e5A72aa52; 52 | address public constant GBPT = 0x86B4dBE5D203e634a12364C0e428fa242A3FbA98; 53 | 54 | // Token Decimals 55 | uint256 public constant USDC_DECIMALS = 6; 56 | uint256 public constant CADC_DECIMALS = 18; 57 | uint256 public constant EUROC_DECIMALS = 6; 58 | uint256 public constant XSGD_DECIMALS = 6; 59 | uint256 public constant NZDS_DECIMALS = 6; 60 | uint256 public constant TRYB_DECIMALS = 6; 61 | uint256 public constant GYEN_DECIMALS = 6; 62 | uint256 public constant XIDR_DECIMALS = 6; 63 | uint256 public constant GBPT_DECIMALS = 18; 64 | 65 | // Oracles 66 | address public constant CHAINLINK_USDC_USD = 0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6; 67 | address public constant CHAINLINK_CAD_USD = 0xa34317DB73e77d453b1B8d04550c44D10e981C8e; 68 | address public constant CHAINLINK_EUR_USD = 0xb49f677943BC038e9857d61E7d053CaA2C1734C1; 69 | address public constant CHAINLINK_SGD_USD = 0xe25277fF4bbF9081C75Ab0EB13B4A13a721f3E13; 70 | address public constant CHAINLINK_NZD_USD = 0x3977CFc9e4f29C184D4675f4EB8e0013236e5f3e; 71 | address public constant CHAINLINK_TRY_USD = 0xB09fC5fD3f11Cf9eb5E1C5Dba43114e3C9f477b5; 72 | address public constant CHAINLINK_YEN_USD = 0xBcE206caE7f0ec07b545EddE332A47C2F75bbeb3; 73 | address public constant CHAINLINK_IDR_USD = 0x91b99C9b75aF469a71eE1AB528e8da994A5D7030; 74 | address public constant CHAINLINK_GBP_USD = 0x5c0Ab2d9b5a7ed9f470386e82BB36A3613cDd4b5; 75 | 76 | // Epsilon (Pool Fee) 77 | uint256 public constant CADC_EPSILON = 15e14; // (0.15%) 78 | uint256 public constant EUROC_EPSILON = 15e14; // (0.15%) 79 | uint256 public constant XSGD_EPSILON = 15e14; // (0.15%) 80 | uint256 public constant NZDS_EPSILON = 3e15; // (0.30%) 81 | uint256 public constant TRYB_EPSILON = 5e15; // (0.50%) 82 | uint256 public constant GYEN_EPSILON = 15e14; // (0.15%) 83 | uint256 public constant XIDR_EPSILON = 5e15; // (0.50%) 84 | uint256 public constant GBPT_EPSILON = 15e14; // (0.15%) 85 | } 86 | 87 | library Arbitrum { 88 | address public constant MULTISIG = 0x69eb9E932A1aBAb2a17893Fa1b0D377602D2F199; 89 | address public constant CURVE_FACTORY = 0x9544995B5312B26acDf09e66E699c34310b7c856; 90 | 91 | // Tokens 92 | address public constant USDC = 0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8; 93 | address public constant EUROC = 0x863708032B5c328e11aBcbC0DF9D79C71Fc52a48; 94 | address public constant GYEN = 0x589d35656641d6aB57A545F08cf473eCD9B6D5F7; 95 | address public constant CADC = 0x2b28E826b55e399F4d4699b85f68666AC51e6f70; 96 | 97 | // Oracles 98 | address public constant CHAINLINK_USDC_USD = 0x50834F3163758fcC1Df9973b6e91f0F0F0434aD3; 99 | address public constant CHAINLINK_EUR_USD = 0xA14d53bC1F1c0F31B4aA3BD109344E5009051a84; 100 | address public constant CHAINLINK_YEN_USD = 0x3dD6e51CB9caE717d5a8778CF79A04029f9cFDF8; 101 | address public constant CHAINLINK_CAD_USD = 0xf6DA27749484843c4F02f5Ad1378ceE723dD61d4; 102 | 103 | // Epsilon (Pool Fee) 104 | // https://data.chain.link/arbitrum/mainnet/fiat 105 | uint256 public constant EUROC_EPSILON = 1e15; // (0.10%) 106 | uint256 public constant GYEN_EPSILON = 3e15; // (0.30%) 107 | uint256 public constant CADC_EPSILON = 3e15; // (0.30%) 108 | } 109 | -------------------------------------------------------------------------------- /script/ArbitrumDeployment.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import "forge-std/Script.sol"; 5 | import "./CurveParams.sol"; 6 | 7 | // Libraries 8 | import "../src/Curve.sol"; 9 | import "../src/Config.sol"; 10 | 11 | // Factories 12 | import "../src/CurveFactoryV2.sol"; 13 | 14 | import "./Addresses.sol"; 15 | import '../src/interfaces/IERC20Detailed.sol'; 16 | 17 | // ARBITRUM DEPLOYMENT 18 | contract ContractScript is Script { 19 | function run() external { 20 | uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); 21 | vm.startBroadcast(deployerPrivateKey); 22 | 23 | // Deploy Assimilator 24 | AssimilatorFactory deployedAssimFactory = new AssimilatorFactory(); 25 | 26 | int128 protocolFee = 50_000; 27 | 28 | Config config = new Config(protocolFee, Arbitrum.MULTISIG); 29 | 30 | 31 | // Deploy CurveFactoryV2 32 | CurveFactoryV2 deployedCurveFactory = new CurveFactoryV2( 33 | address(deployedAssimFactory), 34 | address(config) 35 | ); 36 | 37 | // Attach CurveFactoryV2 to Assimilator 38 | deployedAssimFactory.setCurveFactory(address(deployedCurveFactory)); 39 | 40 | IOracle usdOracle = IOracle(Arbitrum.CHAINLINK_USDC_USD); 41 | IOracle eurOracle = IOracle(Arbitrum.CHAINLINK_EUR_USD); 42 | 43 | CurveInfo memory eurocCurveInfo = CurveInfo( 44 | "dfx-euroc-usdc-v2", 45 | "dfx-euroc-v2", 46 | Arbitrum.EUROC, 47 | Arbitrum.USDC, 48 | CurveParams.BASE_WEIGHT, 49 | CurveParams.QUOTE_WEIGHT, 50 | eurOracle, 51 | usdOracle, 52 | CurveParams.ALPHA, 53 | CurveParams.BETA, 54 | CurveParams.MAX, 55 | Arbitrum.EUROC_EPSILON, 56 | CurveParams.LAMBDA 57 | ); 58 | 59 | // Deploy all new Curves 60 | deployedCurveFactory.newCurve(eurocCurveInfo); 61 | 62 | vm.stopBroadcast(); 63 | } 64 | } -------------------------------------------------------------------------------- /script/ArbitrumGYENCADC.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import "forge-std/Script.sol"; 5 | import "./CurveParams.sol"; 6 | 7 | // Libraries 8 | import "../src/Curve.sol"; 9 | import "../src/Config.sol"; 10 | import "../src/Zap.sol"; 11 | import "../src/Router.sol"; 12 | 13 | // Factories 14 | import "../src/CurveFactoryV2.sol"; 15 | 16 | import "./Addresses.sol"; 17 | import '../src/interfaces/IERC20Detailed.sol'; 18 | 19 | // Arbitrum DEPLOYMENT 20 | contract GYENCADCScript is Script { 21 | function run() external { 22 | uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); 23 | vm.startBroadcast(deployerPrivateKey); 24 | 25 | // Deploy CurveFactoryV2 26 | CurveFactoryV2 deployedCurveFactory = CurveFactoryV2(Arbitrum.CURVE_FACTORY); 27 | 28 | IOracle usdOracle = IOracle(Arbitrum.CHAINLINK_USDC_USD); 29 | IOracle yenOracle = IOracle(Arbitrum.CHAINLINK_YEN_USD); 30 | IOracle cadOracle = IOracle(Arbitrum.CHAINLINK_CAD_USD); 31 | 32 | CurveInfo memory gyenCurveInfo = CurveInfo( 33 | "dfx-gyen-usdc-v2", 34 | "dfx-gyen-v2", 35 | Arbitrum.GYEN, 36 | Arbitrum.USDC, 37 | CurveParams.BASE_WEIGHT, 38 | CurveParams.QUOTE_WEIGHT, 39 | yenOracle, 40 | usdOracle, 41 | CurveParams.ALPHA, 42 | CurveParams.BETA, 43 | CurveParams.MAX, 44 | Arbitrum.GYEN_EPSILON, 45 | CurveParams.LAMBDA 46 | ); 47 | 48 | CurveInfo memory cadcCurveInfo = CurveInfo( 49 | "dfx-cadc-usdc-v2", 50 | "dfx-cadc-v2", 51 | Arbitrum.CADC, 52 | Arbitrum.USDC, 53 | CurveParams.BASE_WEIGHT, 54 | CurveParams.QUOTE_WEIGHT, 55 | cadOracle, 56 | usdOracle, 57 | CurveParams.ALPHA, 58 | CurveParams.BETA, 59 | CurveParams.MAX, 60 | Arbitrum.CADC_EPSILON, 61 | CurveParams.LAMBDA 62 | ); 63 | 64 | // Deploy all new Curves 65 | deployedCurveFactory.newCurve(gyenCurveInfo); 66 | deployedCurveFactory.newCurve(cadcCurveInfo); 67 | 68 | vm.stopBroadcast(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /script/ArbitrumRouterZap.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import "forge-std/Script.sol"; 5 | import "./CurveParams.sol"; 6 | 7 | // Libraries 8 | import "../src/Curve.sol"; 9 | import "../src/Config.sol"; 10 | import "../src/Zap.sol"; 11 | import "../src/Router.sol"; 12 | 13 | // Factories 14 | import "../src/CurveFactoryV2.sol"; 15 | 16 | import "./Addresses.sol"; 17 | import '../src/interfaces/IERC20Detailed.sol'; 18 | 19 | // ARBITRUM DEPLOYMENT 20 | contract ContractScript is Script { 21 | function run() external { 22 | uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); 23 | vm.startBroadcast(deployerPrivateKey); 24 | 25 | Router deployedRouter = new Router(0x9544995B5312B26acDf09e66E699c34310b7c856); 26 | Zap deployedZap = new Zap(); 27 | 28 | // Deploy Assimilator 29 | // AssimilatorFactory deployedAssimFactory = new AssimilatorFactory(); 30 | 31 | // int128 protocolFee = 50_000; 32 | 33 | // Config config = new Config(protocolFee, Arbitrum.MULTISIG); 34 | 35 | 36 | // Deploy CurveFactoryV2 37 | // CurveFactoryV2 deployedCurveFactory = new CurveFactoryV2( 38 | // address(deployedAssimFactory), 39 | // address(config) 40 | // ); 41 | 42 | // Attach CurveFactoryV2 to Assimilator 43 | // deployedAssimFactory.setCurveFactory(address(deployedCurveFactory)); 44 | 45 | // IOracle usdOracle = IOracle(Arbitrum.CHAINLINK_USDC_USD); 46 | // IOracle eurOracle = IOracle(Arbitrum.CHAINLINK_EUR_USD); 47 | 48 | // CurveInfo memory eurocCurveInfo = CurveInfo( 49 | // "dfx-euroc-usdc-v2", 50 | // "dfx-euroc-v2", 51 | // Arbitrum.EUROC, 52 | // Arbitrum.USDC, 53 | // CurveParams.BASE_WEIGHT, 54 | // CurveParams.QUOTE_WEIGHT, 55 | // eurOracle, 56 | // usdOracle, 57 | // CurveParams.ALPHA, 58 | // CurveParams.BETA, 59 | // CurveParams.MAX, 60 | // Arbitrum.EUROC_EPSILON, 61 | // CurveParams.LAMBDA 62 | // ); 63 | 64 | // Deploy all new Curves 65 | // deployedCurveFactory.newCurve(eurocCurveInfo); 66 | 67 | vm.stopBroadcast(); 68 | } 69 | } -------------------------------------------------------------------------------- /script/CurveParams.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | library CurveParams { 5 | // Default Curve Params 6 | uint256 public constant ALPHA = 5e17; 7 | uint256 public constant BETA = 35e16; 8 | uint256 public constant MAX = 15e16; 9 | // uint256 public constant EPSILON = CUSTOM FOR EACH CURVE 10 | uint256 public constant LAMBDA = 1e18; 11 | 12 | // Weights 13 | uint256 public constant BASE_WEIGHT = 5e17; 14 | uint256 public constant QUOTE_WEIGHT = 5e17; 15 | } 16 | -------------------------------------------------------------------------------- /script/MainnetDeployment.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import "forge-std/Script.sol"; 5 | import "./CurveParams.sol"; 6 | 7 | // Libraries 8 | import "../src/Curve.sol"; 9 | import "../src/Config.sol"; 10 | import "../src/Zap.sol"; 11 | import "../src/Router.sol"; 12 | 13 | // Factories 14 | import "../src/CurveFactoryV2.sol"; 15 | 16 | import "./Addresses.sol"; 17 | import '../src/interfaces/IERC20Detailed.sol'; 18 | 19 | // Mainnet DEPLOYMENT 20 | contract MainnetScript is Script { 21 | function run() external { 22 | uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); 23 | vm.startBroadcast(deployerPrivateKey); 24 | 25 | // Deploy Assimilator 26 | // AssimilatorFactory deployedAssimFactory = new AssimilatorFactory(); 27 | 28 | // int128 protocolFee = 50_000; 29 | 30 | // Config config = new Config(protocolFee, Mainnet.MULTISIG); 31 | Config config = Config(0xFC7B7795aa5D8a813b9bbF4D7f2cc05Df5aA843a); 32 | config.toggleGlobalGuarded(); 33 | config.setGlobalGuardAmount(100_000e18); 34 | Router deployedRouter = new Router(0x5dccE0942B2Be4Fd70Cb052c1A51DACde1f0Fe89); 35 | Zap deployedZap = new Zap(); 36 | 37 | // Deploy CurveFactoryV2 38 | // CurveFactoryV2 deployedCurveFactory = new CurveFactoryV2( 39 | // address(deployedAssimFactory), 40 | // address(config) 41 | // ); 42 | 43 | // Attach CurveFactoryV2 to Assimilator 44 | // deployedAssimFactory.setCurveFactory(address(deployedCurveFactory)); 45 | 46 | IOracle usdOracle = IOracle(Mainnet.CHAINLINK_USDC_USD); 47 | // IOracle cadOracle = IOracle(Mainnet.CHAINLINK_CAD_USD); 48 | // IOracle eurOracle = IOracle(Mainnet.CHAINLINK_EUR_USD); 49 | // IOracle sgdOracle = IOracle(Mainnet.CHAINLINK_SGD_USD); 50 | // IOracle nzdOracle = IOracle(Mainnet.CHAINLINK_NZD_USD); 51 | // IOracle tryOracle = IOracle(Mainnet.CHAINLINK_TRY_USD); 52 | // IOracle yenOracle = IOracle(Mainnet.CHAINLINK_YEN_USD); 53 | // IOracle idrOracle = IOracle(Mainnet.CHAINLINK_IDR_USD); 54 | // IOracle gbpOracle = IOracle(Mainnet.); 55 | 56 | // CurveInfo memory cadcCurveInfo = CurveInfo( 57 | // "dfx-cadc-usdc-v2", 58 | // "dfx-cadc-v2", 59 | // Mainnet.CADC, 60 | // Mainnet.USDC, 61 | // CurveParams.BASE_WEIGHT, 62 | // CurveParams.QUOTE_WEIGHT, 63 | // cadOracle, 64 | // usdOracle, 65 | // CurveParams.ALPHA, 66 | // CurveParams.BETA, 67 | // CurveParams.MAX, 68 | // Mainnet.CADC_EPSILON, 69 | // CurveParams.LAMBDA 70 | // ); 71 | 72 | // CurveInfo memory eurocCurveInfo = CurveInfo( 73 | // "dfx-euroc-usdc-v2", 74 | // "dfx-euroc-v2", 75 | // Mainnet.EUROC, 76 | // Mainnet.USDC, 77 | // CurveParams.BASE_WEIGHT, 78 | // CurveParams.QUOTE_WEIGHT, 79 | // eurOracle, 80 | // usdOracle, 81 | // CurveParams.ALPHA, 82 | // CurveParams.BETA, 83 | // CurveParams.MAX, 84 | // Mainnet.EUROC_EPSILON, 85 | // CurveParams.LAMBDA 86 | // ); 87 | 88 | // CurveInfo memory xsgdCurveInfo = CurveInfo( 89 | // "dfx-xsgd-usdc-v2", 90 | // "dfx-xsgd-v2", 91 | // Mainnet.XSGD, 92 | // Mainnet.USDC, 93 | // CurveParams.BASE_WEIGHT, 94 | // CurveParams.QUOTE_WEIGHT, 95 | // sgdOracle, 96 | // usdOracle, 97 | // CurveParams.ALPHA, 98 | // CurveParams.BETA, 99 | // CurveParams.MAX, 100 | // Mainnet.XSGD_EPSILON, 101 | // CurveParams.LAMBDA 102 | // ); 103 | 104 | // CurveInfo memory nzdsCurveInfo = CurveInfo( 105 | // "dfx-nzds-usdc-v2", 106 | // "dfx-nzds-v2", 107 | // Mainnet.NZDS, 108 | // Mainnet.USDC, 109 | // CurveParams.BASE_WEIGHT, 110 | // CurveParams.QUOTE_WEIGHT, 111 | // nzdOracle, 112 | // usdOracle, 113 | // CurveParams.ALPHA, 114 | // CurveParams.BETA, 115 | // CurveParams.MAX, 116 | // Mainnet.NZDS_EPSILON, 117 | // CurveParams.LAMBDA 118 | // ); 119 | 120 | // CurveInfo memory trybCurveInfo = CurveInfo( 121 | // "dfx-tryb-usdc-v2", 122 | // "dfx-tryb-v2", 123 | // Mainnet.TRYB, 124 | // Mainnet.USDC, 125 | // CurveParams.BASE_WEIGHT, 126 | // CurveParams.QUOTE_WEIGHT, 127 | // tryOracle, 128 | // usdOracle, 129 | // CurveParams.ALPHA, 130 | // CurveParams.BETA, 131 | // CurveParams.MAX, 132 | // Mainnet.TRYB_EPSILON, 133 | // CurveParams.LAMBDA 134 | // ); 135 | 136 | // CurveInfo memory gyenCurveInfo = CurveInfo( 137 | // "dfx-gyen-usdc-v2", 138 | // "dfx-gyen-v2", 139 | // Mainnet.GYEN, 140 | // Mainnet.USDC, 141 | // CurveParams.BASE_WEIGHT, 142 | // CurveParams.QUOTE_WEIGHT, 143 | // yenOracle, 144 | // usdOracle, 145 | // CurveParams.ALPHA, 146 | // CurveParams.BETA, 147 | // CurveParams.MAX, 148 | // Mainnet.GYEN_EPSILON, 149 | // CurveParams.LAMBDA 150 | // ); 151 | 152 | // CurveInfo memory xidrCurveInfo = CurveInfo( 153 | // "dfx-xidr-usdc-v2", 154 | // "dfx-xidr-v2", 155 | // Mainnet.XIDR, 156 | // Mainnet.USDC, 157 | // CurveParams.BASE_WEIGHT, 158 | // CurveParams.QUOTE_WEIGHT, 159 | // idrOracle, 160 | // usdOracle, 161 | // CurveParams.ALPHA, 162 | // CurveParams.BETA, 163 | // CurveParams.MAX, 164 | // Mainnet.XIDR_EPSILON, 165 | // CurveParams.LAMBDA 166 | // ); 167 | 168 | // Deploy all new Curves 169 | // deployedCurveFactory.newCurve(cadcCurveInfo); 170 | // deployedCurveFactory.newCurve(eurocCurveInfo); 171 | // deployedCurveFactory.newCurve(xsgdCurveInfo); 172 | // deployedCurveFactory.newCurve(nzdsCurveInfo); 173 | // deployedCurveFactory.newCurve(trybCurveInfo); 174 | // deployedCurveFactory.newCurve(gyenCurveInfo); 175 | // deployedCurveFactory.newCurve(xidrCurveInfo); 176 | 177 | vm.stopBroadcast(); 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /script/MainnetGBPT.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import "forge-std/Script.sol"; 5 | import "./CurveParams.sol"; 6 | 7 | // Libraries 8 | import "../src/Curve.sol"; 9 | import "../src/Config.sol"; 10 | import "../src/Zap.sol"; 11 | import "../src/Router.sol"; 12 | 13 | // Factories 14 | import "../src/CurveFactoryV2.sol"; 15 | 16 | import "./Addresses.sol"; 17 | import '../src/interfaces/IERC20Detailed.sol'; 18 | 19 | // Mainnet DEPLOYMENT 20 | contract GBPTScript is Script { 21 | function run() external { 22 | uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); 23 | vm.startBroadcast(deployerPrivateKey); 24 | 25 | // Deploy CurveFactoryV2 26 | CurveFactoryV2 deployedCurveFactory = CurveFactoryV2(Mainnet.CURVE_FACTORY); 27 | 28 | IOracle usdOracle = IOracle(Mainnet.CHAINLINK_USDC_USD); 29 | IOracle gbpOracle = IOracle(Mainnet.CHAINLINK_GBP_USD); 30 | 31 | CurveInfo memory gbptCurveInfo = CurveInfo( 32 | "dfx-gbpt-usdc-v2", 33 | "dfx-gbpt-v2", 34 | Mainnet.GBPT, 35 | Mainnet.USDC, 36 | CurveParams.BASE_WEIGHT, 37 | CurveParams.QUOTE_WEIGHT, 38 | gbpOracle, 39 | usdOracle, 40 | CurveParams.ALPHA, 41 | CurveParams.BETA, 42 | CurveParams.MAX, 43 | Mainnet.GBPT_EPSILON, 44 | CurveParams.LAMBDA 45 | ); 46 | 47 | // Deploy all new Curves 48 | deployedCurveFactory.newCurve(gbptCurveInfo); 49 | 50 | vm.stopBroadcast(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /script/PolygonDeployment.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import "forge-std/Script.sol"; 5 | import "./CurveParams.sol"; 6 | 7 | // Libraries 8 | import "../src/Curve.sol"; 9 | import "../src/Config.sol"; 10 | 11 | // Factories 12 | import "../src/CurveFactoryV2.sol"; 13 | 14 | import "./Addresses.sol"; 15 | import '../src/interfaces/IERC20Detailed.sol'; 16 | 17 | // POLYGON DEPLOYMENT 18 | contract ContractScript is Script { 19 | function run() external { 20 | uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); 21 | vm.startBroadcast(deployerPrivateKey); 22 | 23 | // Deploy Assimilator 24 | AssimilatorFactory deployedAssimFactory = new AssimilatorFactory(); 25 | 26 | int128 protocolFee = 50_000; 27 | 28 | Config config = new Config(protocolFee, Polygon.MULTISIG); 29 | 30 | 31 | // Deploy CurveFactoryV2 32 | CurveFactoryV2 deployedCurveFactory = new CurveFactoryV2( 33 | address(deployedAssimFactory), 34 | address(config) 35 | ); 36 | 37 | // Attach CurveFactoryV2 to Assimilator 38 | deployedAssimFactory.setCurveFactory(address(deployedCurveFactory)); 39 | 40 | IOracle usdOracle = IOracle(Polygon.CHAINLINK_USDC_USD); 41 | IOracle cadOracle = IOracle(Polygon.CHAINLINK_CAD_USD); 42 | IOracle eurOracle = IOracle(Polygon.CHAINLINK_EUR_USD); 43 | IOracle sgdOracle = IOracle(Polygon.CHAINLINK_SGD_USD); 44 | IOracle nzdOracle = IOracle(Polygon.CHAINLINK_NZD_USD); 45 | IOracle tryOracle = IOracle(Polygon.CHAINLINK_TRY_USD); 46 | 47 | CurveInfo memory cadcCurveInfo = CurveInfo( 48 | "dfx-cadc-usdc-v2", 49 | "dfx-cadc-v2", 50 | Polygon.CADC, 51 | Polygon.USDC, 52 | CurveParams.BASE_WEIGHT, 53 | CurveParams.QUOTE_WEIGHT, 54 | cadOracle, 55 | usdOracle, 56 | CurveParams.ALPHA, 57 | CurveParams.BETA, 58 | CurveParams.MAX, 59 | Polygon.CADC_EPSILON, 60 | CurveParams.LAMBDA 61 | ); 62 | 63 | CurveInfo memory eursCurveInfo = CurveInfo( 64 | "dfx-eurs-usdc-v2", 65 | "dfx-eurs-v2", 66 | Polygon.EURS, 67 | Polygon.USDC, 68 | CurveParams.BASE_WEIGHT, 69 | CurveParams.QUOTE_WEIGHT, 70 | eurOracle, 71 | usdOracle, 72 | CurveParams.ALPHA, 73 | CurveParams.BETA, 74 | CurveParams.MAX, 75 | Polygon.EURS_EPSILON, 76 | CurveParams.LAMBDA 77 | ); 78 | 79 | CurveInfo memory xsgdCurveInfo = CurveInfo( 80 | "dfx-xsgd-usdc-v2", 81 | "dfx-xsgd-v2", 82 | Polygon.XSGD, 83 | Polygon.USDC, 84 | CurveParams.BASE_WEIGHT, 85 | CurveParams.QUOTE_WEIGHT, 86 | sgdOracle, 87 | usdOracle, 88 | CurveParams.ALPHA, 89 | CurveParams.BETA, 90 | CurveParams.MAX, 91 | Polygon.XSGD_EPSILON, 92 | CurveParams.LAMBDA 93 | ); 94 | 95 | CurveInfo memory nzdsCurveInfo = CurveInfo( 96 | "dfx-nzds-usdc-v2", 97 | "dfx-nzds-v2", 98 | Polygon.NZDS, 99 | Polygon.USDC, 100 | CurveParams.BASE_WEIGHT, 101 | CurveParams.QUOTE_WEIGHT, 102 | nzdOracle, 103 | usdOracle, 104 | CurveParams.ALPHA, 105 | CurveParams.BETA, 106 | CurveParams.MAX, 107 | Polygon.NZDS_EPSILON, 108 | CurveParams.LAMBDA 109 | ); 110 | 111 | CurveInfo memory trybCurveInfo = CurveInfo( 112 | "dfx-tryb-usdc-v2", 113 | "dfx-tryb-v2", 114 | Polygon.TRYB, 115 | Polygon.USDC, 116 | CurveParams.BASE_WEIGHT, 117 | CurveParams.QUOTE_WEIGHT, 118 | tryOracle, 119 | usdOracle, 120 | CurveParams.ALPHA, 121 | CurveParams.BETA, 122 | CurveParams.MAX, 123 | Polygon.TRYB_EPSILON, 124 | CurveParams.LAMBDA 125 | ); 126 | 127 | // Deploy all new Curves 128 | deployedCurveFactory.newCurve(cadcCurveInfo); 129 | deployedCurveFactory.newCurve(eursCurveInfo); 130 | deployedCurveFactory.newCurve(xsgdCurveInfo); 131 | deployedCurveFactory.newCurve(nzdsCurveInfo); 132 | deployedCurveFactory.newCurve(trybCurveInfo); 133 | 134 | vm.stopBroadcast(); 135 | } 136 | } -------------------------------------------------------------------------------- /src/AssimilatorFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import "./assimilators/AssimilatorV2.sol"; 5 | import "../lib/openzeppelin-contracts/contracts/access/Ownable.sol"; 6 | import "./interfaces/IAssimilatorFactory.sol"; 7 | import "./interfaces/IOracle.sol"; 8 | 9 | contract AssimilatorFactory is IAssimilatorFactory, Ownable { 10 | event NewAssimilator( 11 | address indexed caller, 12 | bytes32 indexed id, 13 | address indexed assimilator, 14 | address oracle, 15 | address token 16 | ); 17 | event AssimilatorRevoked( 18 | address indexed caller, 19 | bytes32 indexed id, 20 | address indexed assimilator 21 | ); 22 | event CurveFactoryUpdated( 23 | address indexed caller, 24 | address indexed curveFactory 25 | ); 26 | mapping(bytes32 => AssimilatorV2) public assimilators; 27 | 28 | address public curveFactory; 29 | 30 | modifier onlyCurveFactoryOrOwner { 31 | require(msg.sender == curveFactory || msg.sender == owner(), "unauthorized"); 32 | _; 33 | } 34 | 35 | function setCurveFactory(address _curveFactory) external onlyOwner { 36 | require( 37 | _curveFactory != address(0), 38 | "AssimFactory/curve factory zero address!" 39 | ); 40 | curveFactory = _curveFactory; 41 | emit CurveFactoryUpdated(msg.sender, curveFactory); 42 | } 43 | 44 | function getAssimilator(address _token) 45 | external 46 | view 47 | override 48 | returns (AssimilatorV2) 49 | { 50 | bytes32 assimilatorID = keccak256(abi.encode(_token)); 51 | return assimilators[assimilatorID]; 52 | } 53 | 54 | function newAssimilator( 55 | IOracle _oracle, 56 | address _token, 57 | uint256 _tokenDecimals 58 | ) external override onlyCurveFactoryOrOwner returns (AssimilatorV2) { 59 | bytes32 assimilatorID = keccak256(abi.encode(_token)); 60 | if (address(assimilators[assimilatorID]) != address(0)) 61 | revert("AssimilatorFactory/oracle-stablecoin-pair-already-exists"); 62 | AssimilatorV2 assimilator = new AssimilatorV2( 63 | _oracle, 64 | _token, 65 | _tokenDecimals, 66 | IOracle(_oracle).decimals() 67 | ); 68 | assimilators[assimilatorID] = assimilator; 69 | emit NewAssimilator( 70 | msg.sender, 71 | assimilatorID, 72 | address(assimilator), 73 | address(_oracle), 74 | _token 75 | ); 76 | return assimilator; 77 | } 78 | 79 | function revokeAssimilator(address _token) external onlyOwner { 80 | bytes32 assimilatorID = keccak256(abi.encode(_token)); 81 | address _assimAddress = address(assimilators[assimilatorID]); 82 | assimilators[assimilatorID] = AssimilatorV2(address(0)); 83 | emit AssimilatorRevoked( 84 | msg.sender, 85 | assimilatorID, 86 | address(_assimAddress) 87 | ); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/Assimilators.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | pragma solidity ^0.8.13; 17 | 18 | import "../lib/openzeppelin-contracts/contracts/utils/Address.sol"; 19 | import "./interfaces/IAssimilator.sol"; 20 | import "./lib/ABDKMath64x64.sol"; 21 | import "./Structs.sol"; 22 | 23 | library Assimilators { 24 | using ABDKMath64x64 for int128; 25 | using Address for address; 26 | 27 | IAssimilator public constant iAsmltr = IAssimilator(address(0)); 28 | 29 | function delegate(address _callee, bytes memory _data) internal returns (bytes memory) { 30 | require(_callee.isContract(), "Assimilators/callee-is-not-a-contract"); 31 | 32 | // solhint-disable-next-line 33 | (bool _success, bytes memory returnData_) = _callee.delegatecall(_data); 34 | 35 | // solhint-disable-next-line 36 | assembly { 37 | if eq(_success, 0) { 38 | revert(add(returnData_, 0x20), returndatasize()) 39 | } 40 | } 41 | 42 | return returnData_; 43 | } 44 | 45 | function getRate(address _assim) internal view returns (uint256 amount_) { 46 | amount_ = IAssimilator(_assim).getRate(); 47 | } 48 | 49 | function viewRawAmount(address _assim, int128 _amt) internal view returns (uint256 amount_) { 50 | amount_ = IAssimilator(_assim).viewRawAmount(_amt); 51 | } 52 | 53 | function viewRawAmountLPRatio( 54 | address _assim, 55 | uint256 _baseWeight, 56 | uint256 _quoteWeight, 57 | int128 _amount 58 | ) internal view returns (uint256 amount_) { 59 | amount_ = IAssimilator(_assim).viewRawAmountLPRatio(_baseWeight, _quoteWeight, address(this), _amount); 60 | } 61 | 62 | function viewNumeraireAmount(address _assim, uint256 _amt) internal view returns (int128 amt_) { 63 | amt_ = IAssimilator(_assim).viewNumeraireAmount(_amt); 64 | } 65 | 66 | function viewNumeraireAmountAndBalance(address _assim, uint256 _amt) 67 | internal 68 | view 69 | returns (int128 amt_, int128 bal_) 70 | { 71 | (amt_, bal_) = IAssimilator(_assim).viewNumeraireAmountAndBalance(address(this), _amt); 72 | } 73 | 74 | function viewNumeraireBalance(address _assim) internal view returns (int128 bal_) { 75 | bal_ = IAssimilator(_assim).viewNumeraireBalance(address(this)); 76 | } 77 | 78 | function viewNumeraireBalanceLPRatio( 79 | uint256 _baseWeight, 80 | uint256 _quoteWeight, 81 | address _assim 82 | ) internal view returns (int128 bal_) { 83 | bal_ = IAssimilator(_assim).viewNumeraireBalanceLPRatio(_baseWeight, _quoteWeight, address(this)); 84 | } 85 | 86 | function intakeRaw(address _assim, uint256 _amt) internal returns (int128 amt_) { 87 | bytes memory data = abi.encodeWithSelector(iAsmltr.intakeRaw.selector, _amt); 88 | 89 | amt_ = abi.decode(delegate(_assim, data), (int128)); 90 | } 91 | 92 | function intakeRawAndGetBalance(address _assim, uint256 _amt) internal returns (int128 amt_, int128 bal_) { 93 | bytes memory data = abi.encodeWithSelector(iAsmltr.intakeRawAndGetBalance.selector, _amt); 94 | 95 | (amt_, bal_) = abi.decode(delegate(_assim, data), (int128, int128)); 96 | } 97 | 98 | function intakeNumeraire(address _assim, int128 _amt) internal returns (uint256 amt_) { 99 | bytes memory data = abi.encodeWithSelector(iAsmltr.intakeNumeraire.selector, _amt); 100 | 101 | amt_ = abi.decode(delegate(_assim, data), (uint256)); 102 | } 103 | 104 | function intakeNumeraireLPRatio( 105 | address _assim, 106 | IntakeNumLpRatioInfo memory info 107 | ) internal returns (uint256 amt_) { 108 | bytes memory data = abi.encodeWithSelector( 109 | iAsmltr.intakeNumeraireLPRatio.selector, 110 | info.baseWeight, 111 | info.minBase, 112 | info.maxBase, 113 | info.quoteWeight, 114 | info.minQuote, 115 | info.maxQuote, 116 | address(this), 117 | // _amount 118 | info.amount 119 | ); 120 | 121 | amt_ = abi.decode(delegate(_assim, data), (uint256)); 122 | } 123 | 124 | function outputRaw( 125 | address _assim, 126 | address _dst, 127 | uint256 _amt 128 | ) internal returns (int128 amt_) { 129 | bytes memory data = abi.encodeWithSelector(iAsmltr.outputRaw.selector, _dst, _amt); 130 | 131 | amt_ = abi.decode(delegate(_assim, data), (int128)); 132 | 133 | amt_ = amt_.neg(); 134 | } 135 | 136 | function outputRawAndGetBalance( 137 | address _assim, 138 | address _dst, 139 | uint256 _amt 140 | ) internal returns (int128 amt_, int128 bal_) { 141 | bytes memory data = abi.encodeWithSelector(iAsmltr.outputRawAndGetBalance.selector, _dst, _amt); 142 | 143 | (amt_, bal_) = abi.decode(delegate(_assim, data), (int128, int128)); 144 | 145 | amt_ = amt_.neg(); 146 | } 147 | 148 | function outputNumeraire( 149 | address _assim, 150 | address _dst, 151 | int128 _amt 152 | ) internal returns (uint256 amt_) { 153 | bytes memory data = abi.encodeWithSelector(iAsmltr.outputNumeraire.selector, _dst, _amt.abs()); 154 | 155 | amt_ = abi.decode(delegate(_assim, data), (uint256)); 156 | } 157 | 158 | function transferFee( 159 | address _assim, 160 | int128 _amt, 161 | address _treasury 162 | ) internal { 163 | bytes memory data = abi.encodeWithSelector(iAsmltr.transferFee.selector, _amt, _treasury); 164 | delegate(_assim, data); 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/Config.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.13; 4 | pragma experimental ABIEncoderV2; 5 | 6 | import "../lib/openzeppelin-contracts/contracts/utils/Address.sol"; 7 | import "../lib/openzeppelin-contracts/contracts/access/Ownable.sol"; 8 | import "../lib/openzeppelin-contracts/contracts/security/ReentrancyGuard.sol"; 9 | import "./interfaces/IConfig.sol"; 10 | 11 | contract Config is Ownable, IConfig, ReentrancyGuard { 12 | using Address for address; 13 | 14 | // add protocol fee 15 | int128 public totalFeePercentage = 100000; 16 | int128 public protocolFee; 17 | address public protocolTreasury; 18 | 19 | // Global curve operational state 20 | bool public globalFrozen = false; 21 | bool public flashable = false; 22 | 23 | bool public globalGuarded = false; 24 | mapping (address => bool) public poolGuarded; 25 | 26 | uint256 public globalGuardAmt; 27 | mapping (address => uint256) public poolGuardAmt; 28 | mapping (address => uint256) public poolCapAmt; 29 | 30 | event GlobalFrozenSet(bool isFrozen); 31 | event FlashableSet(bool isFlashable); 32 | event TreasuryUpdated(address indexed newTreasury); 33 | event ProtocolFeeUpdated(address indexed treasury, int128 indexed fee); 34 | event GlobalGuardSet(bool isGuarded); 35 | event GlobalGuardAmountSet (uint256 amount); 36 | event PoolGuardSet (address indexed pool, bool isGuarded); 37 | event PoolGuardAmountSet (address indexed pool, uint256 guardAmount); 38 | event PoolCapSet (address indexed pool, uint256 cap); 39 | 40 | constructor ( 41 | int128 _protocolFee, 42 | address _treasury) { 43 | require(totalFeePercentage >= _protocolFee, "CurveFactory/fee-cant-be-over-100%"); 44 | require(_treasury != address(0), "CurveFactory/zero-address"); 45 | protocolFee = _protocolFee; 46 | protocolTreasury = _treasury; 47 | } 48 | 49 | function getGlobalFrozenState() external view virtual override returns (bool) { 50 | return globalFrozen; 51 | } 52 | 53 | function getFlashableState() external view virtual override returns (bool) { 54 | return flashable; 55 | } 56 | 57 | function getProtocolFee() external view virtual override returns (int128) { 58 | return protocolFee; 59 | } 60 | 61 | function getProtocolTreasury() external view virtual override returns (address) { 62 | return protocolTreasury; 63 | } 64 | 65 | function setGlobalFrozen(bool _toFreezeOrNotToFreeze) external virtual override onlyOwner { 66 | emit GlobalFrozenSet(_toFreezeOrNotToFreeze); 67 | 68 | globalFrozen = _toFreezeOrNotToFreeze; 69 | } 70 | 71 | function toggleGlobalGuarded () external virtual override onlyOwner nonReentrant { 72 | globalGuarded = !globalGuarded; 73 | emit GlobalGuardSet(globalGuarded); 74 | } 75 | 76 | function setPoolGuarded (address pool, bool guarded ) external virtual override onlyOwner nonReentrant { 77 | poolGuarded[pool] = guarded; 78 | emit PoolGuardSet(pool, guarded); 79 | } 80 | 81 | function setGlobalGuardAmount (uint256 amount) external virtual override onlyOwner nonReentrant { 82 | globalGuardAmt = amount - 1e6; 83 | emit GlobalGuardAmountSet (globalGuardAmt); 84 | } 85 | 86 | function setPoolCap (address pool, uint256 cap) external nonReentrant virtual override onlyOwner { 87 | poolCapAmt[pool] = cap; 88 | emit PoolCapSet(pool, cap); 89 | } 90 | 91 | function setPoolGuardAmount (address pool, uint256 amount) external nonReentrant virtual override onlyOwner { 92 | poolGuardAmt[pool] = amount - 1e6; 93 | emit PoolGuardAmountSet(pool, amount); 94 | } 95 | 96 | function isPoolGuarded (address pool) external view override returns (bool) { 97 | bool _poolGuarded = poolGuarded[pool]; 98 | if(!_poolGuarded){ 99 | return globalGuarded; 100 | }else{ 101 | return true; 102 | } 103 | } 104 | 105 | function getPoolGuardAmount (address pool) external view override returns (uint256) { 106 | uint256 _poolGuardAmt = poolGuardAmt[pool]; 107 | if(_poolGuardAmt == 0) { 108 | return globalGuardAmt; 109 | }else{ 110 | return _poolGuardAmt; 111 | } 112 | } 113 | 114 | function getPoolCap (address pool) external view override returns (uint256) { 115 | return poolCapAmt[pool]; 116 | } 117 | 118 | function setFlashable(bool _toFlashOrNotToFlash) external virtual override onlyOwner nonReentrant { 119 | emit FlashableSet(_toFlashOrNotToFlash); 120 | 121 | flashable = _toFlashOrNotToFlash; 122 | } 123 | 124 | function updateProtocolTreasury(address _newTreasury) external virtual override onlyOwner nonReentrant { 125 | require(_newTreasury != protocolTreasury, "CurveFactory/same-treasury-address"); 126 | require(_newTreasury != address(0), "CurveFactory/zero-address"); 127 | protocolTreasury = _newTreasury; 128 | emit TreasuryUpdated(protocolTreasury); 129 | } 130 | 131 | function updateProtocolFee(int128 _newFee) external virtual override onlyOwner nonReentrant { 132 | require(totalFeePercentage >= _newFee, "CurveFactory/fee-cant-be-over-100%"); 133 | require(_newFee != protocolFee, "CurveFactory/same-protocol-fee"); 134 | protocolFee = _newFee; 135 | emit ProtocolFeeUpdated(protocolTreasury, protocolFee); 136 | } 137 | } -------------------------------------------------------------------------------- /src/CurveFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | 8 | // This program is disstributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | pragma solidity ^0.8.13; 17 | 18 | // Finds new Curves! logs their addresses and provides `isCurve(address) -> (bool)` 19 | 20 | import "./Curve.sol"; 21 | 22 | import "./interfaces/IFreeFromUpTo.sol"; 23 | 24 | import "../lib/openzeppelin-contracts/contracts/access/Ownable.sol"; 25 | import "../lib/openzeppelin-contracts/contracts/security/ReentrancyGuard.sol"; 26 | 27 | contract CurveFactory is Ownable, ReentrancyGuard { 28 | event NewCurve(address indexed caller, bytes32 indexed id, address indexed curve); 29 | 30 | mapping(bytes32 => address) public curves; 31 | 32 | function getCurve(address _baseCurrency, address _quoteCurrency) external view returns (address) { 33 | bytes32 curveId = keccak256(abi.encode(_baseCurrency, _quoteCurrency)); 34 | return (curves[curveId]); 35 | } 36 | 37 | function newCurve( 38 | string memory _name, 39 | string memory _symbol, 40 | address _baseCurrency, 41 | address _quoteCurrency, 42 | uint256 _baseWeight, 43 | uint256 _quoteWeight, 44 | address _baseAssimilator, 45 | address _quoteAssimilator 46 | ) public nonReentrant onlyOwner returns (Curve) { 47 | bytes32 curveId = keccak256(abi.encode(_baseCurrency, _quoteCurrency)); 48 | if (curves[curveId] != address(0)) revert("CurveFactory/currency-pair-already-exists"); 49 | 50 | address[] memory _assets = new address[](10); 51 | uint256[] memory _assetWeights = new uint256[](2); 52 | 53 | // Base Currency 54 | _assets[0] = _baseCurrency; 55 | _assets[1] = _baseAssimilator; 56 | _assets[2] = _baseCurrency; 57 | _assets[3] = _baseAssimilator; 58 | _assets[4] = _baseCurrency; 59 | 60 | // Quote Currency (typically USDC) 61 | _assets[5] = _quoteCurrency; 62 | _assets[6] = _quoteAssimilator; 63 | _assets[7] = _quoteCurrency; 64 | _assets[8] = _quoteAssimilator; 65 | _assets[9] = _quoteCurrency; 66 | 67 | // Weights 68 | _assetWeights[0] = _baseWeight; 69 | _assetWeights[1] = _quoteWeight; 70 | 71 | // New curve 72 | Curve curve = new Curve(_name, _symbol, _assets, _assetWeights, address(this)); 73 | curve.transferOwnership(msg.sender); 74 | curves[curveId] = address(curve); 75 | 76 | emit NewCurve(msg.sender, curveId, address(curve)); 77 | 78 | return curve; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/CurveFactoryV2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | 8 | // This program is disstributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | pragma solidity ^0.8.13; 17 | pragma experimental ABIEncoderV2; 18 | 19 | // Finds new Curves! logs their addresses and provides `isCurve(address) -> (bool)` 20 | 21 | import "../lib/openzeppelin-contracts/contracts/utils/Address.sol"; 22 | import "../lib/openzeppelin-contracts/contracts/access/Ownable.sol"; 23 | 24 | import "./Curve.sol"; 25 | import "./interfaces/IFreeFromUpTo.sol"; 26 | import "./AssimilatorFactory.sol"; 27 | import "./assimilators/AssimilatorV2.sol"; 28 | import "./interfaces/ICurveFactory.sol"; 29 | import "./interfaces/IAssimilatorFactory.sol"; 30 | import "./interfaces/IERC20Detailed.sol"; 31 | import "./interfaces/IConfig.sol"; 32 | import "./Structs.sol"; 33 | 34 | contract CurveFactoryV2 is ICurveFactory, Ownable { 35 | using Address for address; 36 | 37 | IAssimilatorFactory public immutable assimilatorFactory; 38 | IConfig public config; 39 | 40 | address private USDC; 41 | 42 | event NewCurve( 43 | address indexed caller, 44 | bytes32 indexed id, 45 | address indexed curve 46 | ); 47 | 48 | mapping(bytes32 => address) public curves; 49 | 50 | constructor(address _assimFactory, address _config) { 51 | require( 52 | _assimFactory.isContract(), 53 | "CurveFactory/invalid-assimFactory" 54 | ); 55 | assimilatorFactory = IAssimilatorFactory(_assimFactory); 56 | require(_config.isContract(), "CurveFactory/invalid-config"); 57 | config = IConfig(_config); 58 | } 59 | 60 | function getGlobalFrozenState() 61 | external 62 | view 63 | virtual 64 | override 65 | returns (bool) 66 | { 67 | return config.getGlobalFrozenState(); 68 | } 69 | 70 | function getFlashableState() external view virtual override returns (bool) { 71 | return config.getFlashableState(); 72 | } 73 | 74 | function getProtocolFee() external view virtual override returns (int128) { 75 | return config.getProtocolFee(); 76 | } 77 | 78 | function getProtocolTreasury() 79 | public 80 | view 81 | virtual 82 | override 83 | returns (address) 84 | { 85 | return config.getProtocolTreasury(); 86 | } 87 | 88 | function isPoolGuarded(address pool) external view override returns (bool) { 89 | return config.isPoolGuarded(pool); 90 | } 91 | 92 | function getPoolGuardAmount(address pool) 93 | external 94 | view 95 | override 96 | returns (uint256) 97 | { 98 | return config.getPoolGuardAmount(pool); 99 | } 100 | 101 | function getPoolCap(address pool) external view override returns (uint256) { 102 | return config.getPoolCap(pool); 103 | } 104 | 105 | function getCurve(address _baseCurrency, address _quoteCurrency) 106 | external 107 | view 108 | returns (address) 109 | { 110 | bytes32 curveId = keccak256(abi.encode(_baseCurrency, _quoteCurrency)); 111 | return (curves[curveId]); 112 | } 113 | 114 | function newCurve(CurveInfo memory _info) public returns (Curve) { 115 | require( 116 | _info._quoteCurrency == quoteAddress(), 117 | "CurveFactory/quote-currency-is-not-usdc" 118 | ); 119 | require( 120 | _info._baseCurrency != quoteAddress(), 121 | "CurveFactory/base-currency-is-usdc" 122 | ); 123 | 124 | require( 125 | _info._baseWeight == 5e17 && _info._quoteWeight == 5e17, 126 | "CurveFactory/weights-not-50-percent" 127 | ); 128 | 129 | uint256 quoteDec = IERC20Detailed(_info._quoteCurrency).decimals(); 130 | uint256 baseDec = IERC20Detailed(_info._baseCurrency).decimals(); 131 | 132 | bytes32 curveId = keccak256( 133 | abi.encode(_info._baseCurrency, _info._quoteCurrency) 134 | ); 135 | if (curves[curveId] != address(0)) revert("CurveFactory/pair-exists"); 136 | AssimilatorV2 _baseAssim; 137 | _baseAssim = (assimilatorFactory.getAssimilator(_info._baseCurrency)); 138 | if (address(_baseAssim) == address(0)) 139 | _baseAssim = ( 140 | assimilatorFactory.newAssimilator( 141 | _info._baseOracle, 142 | _info._baseCurrency, 143 | baseDec 144 | ) 145 | ); 146 | AssimilatorV2 _quoteAssim; 147 | _quoteAssim = (assimilatorFactory.getAssimilator(_info._quoteCurrency)); 148 | if (address(_quoteAssim) == address(0)) 149 | _quoteAssim = ( 150 | assimilatorFactory.newAssimilator( 151 | _info._quoteOracle, 152 | _info._quoteCurrency, 153 | quoteDec 154 | ) 155 | ); 156 | 157 | address[] memory _assets = new address[](10); 158 | uint256[] memory _assetWeights = new uint256[](2); 159 | 160 | // Base Currency 161 | _assets[0] = _info._baseCurrency; 162 | _assets[1] = address(_baseAssim); 163 | _assets[2] = _info._baseCurrency; 164 | _assets[3] = address(_baseAssim); 165 | _assets[4] = _info._baseCurrency; 166 | 167 | // Quote Currency (typically USDC) 168 | _assets[5] = _info._quoteCurrency; 169 | _assets[6] = address(_quoteAssim); 170 | _assets[7] = _info._quoteCurrency; 171 | _assets[8] = address(_quoteAssim); 172 | _assets[9] = _info._quoteCurrency; 173 | 174 | // Weights 175 | _assetWeights[0] = _info._baseWeight; 176 | _assetWeights[1] = _info._quoteWeight; 177 | 178 | // New curve 179 | Curve curve = new Curve( 180 | _info._name, 181 | _info._symbol, 182 | _assets, 183 | _assetWeights, 184 | address(this) 185 | ); 186 | curve.setParams( 187 | _info._alpha, 188 | _info._beta, 189 | _info._feeAtHalt, 190 | _info._epsilon, 191 | _info._lambda 192 | ); 193 | curve.transferOwnership(getProtocolTreasury()); 194 | curves[curveId] = address(curve); 195 | 196 | emit NewCurve(msg.sender, curveId, address(curve)); 197 | 198 | return curve; 199 | } 200 | 201 | function quoteAddress() internal view returns (address) { 202 | if (USDC == address(0)) { 203 | uint256 chainID; 204 | assembly { 205 | chainID := chainid() 206 | } 207 | if (chainID == 1) { 208 | return 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; 209 | } else if (chainID == 137) { 210 | return 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174; 211 | } else if (chainID == 42161) { 212 | return 0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8; 213 | } else { 214 | return address(0); 215 | } 216 | } else { 217 | return USDC; 218 | } 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /src/CurveMath.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | pragma solidity ^0.8.13; 17 | 18 | import "./Storage.sol"; 19 | 20 | import "./lib/UnsafeMath64x64.sol"; 21 | import "./lib/ABDKMath64x64.sol"; 22 | 23 | library CurveMath { 24 | int128 private constant ONE = 0x10000000000000000; 25 | int128 private constant MAX = 0x4000000000000000; // .25 in layman's terms 26 | int128 private constant MAX_DIFF = -0x10C6F7A0B5EE; 27 | int128 private constant ONE_WEI = 0x12; 28 | 29 | using ABDKMath64x64 for int128; 30 | using UnsafeMath64x64 for int128; 31 | using ABDKMath64x64 for uint256; 32 | 33 | // This is used to prevent stack too deep errors 34 | function calculateFee( 35 | int128 _gLiq, 36 | int128[] memory _bals, 37 | Storage.Curve storage curve, 38 | int128[] memory _weights 39 | ) internal view returns (int128 psi_) { 40 | int128 _beta = curve.beta; 41 | int128 _delta = curve.delta; 42 | 43 | psi_ = calculateFee(_gLiq, _bals, _beta, _delta, _weights); 44 | } 45 | 46 | function calculateFee( 47 | int128 _gLiq, 48 | int128[] memory _bals, 49 | int128 _beta, 50 | int128 _delta, 51 | int128[] memory _weights 52 | ) internal pure returns (int128 psi_) { 53 | uint256 _length = _bals.length; 54 | 55 | for (uint256 i = 0; i < _length; i++) { 56 | int128 _ideal = _gLiq.mul(_weights[i]); 57 | psi_ += calculateMicroFee(_bals[i], _ideal, _beta, _delta); 58 | } 59 | } 60 | 61 | function calculateMicroFee( 62 | int128 _bal, 63 | int128 _ideal, 64 | int128 _beta, 65 | int128 _delta 66 | ) private pure returns (int128 fee_) { 67 | if (_bal < _ideal) { 68 | int128 _threshold = _ideal.mul(ONE - _beta); 69 | 70 | if (_bal < _threshold) { 71 | int128 _feeMargin = _threshold - _bal; 72 | 73 | fee_ = _feeMargin.mul(_delta); 74 | fee_ = fee_.div(_ideal); 75 | 76 | if (fee_ > MAX) fee_ = MAX; 77 | 78 | fee_ = fee_.mul(_feeMargin); 79 | } else fee_ = 0; 80 | } else { 81 | int128 _threshold = _ideal.mul(ONE + _beta); 82 | 83 | if (_bal > _threshold) { 84 | int128 _feeMargin = _bal - _threshold; 85 | 86 | fee_ = _feeMargin.mul(_delta); 87 | fee_ = fee_.div(_ideal); 88 | 89 | if (fee_ > MAX) fee_ = MAX; 90 | 91 | fee_ = fee_.mul(_feeMargin); 92 | } else fee_ = 0; 93 | } 94 | } 95 | 96 | function calculateTrade( 97 | Storage.Curve storage curve, 98 | int128 _oGLiq, 99 | int128 _nGLiq, 100 | int128[] memory _oBals, 101 | int128[] memory _nBals, 102 | int128 _inputAmt, 103 | uint256 _outputIndex 104 | ) internal view returns (int128 outputAmt_) { 105 | outputAmt_ = -_inputAmt; 106 | 107 | int128 _lambda = curve.lambda; 108 | int128[] memory _weights = curve.weights; 109 | 110 | int128 _omega = calculateFee(_oGLiq, _oBals, curve, _weights); 111 | int128 _psi; 112 | 113 | for (uint256 i = 0; i < 32; i++) { 114 | _psi = calculateFee(_nGLiq, _nBals, curve, _weights); 115 | 116 | int128 prevAmount; 117 | { 118 | prevAmount = outputAmt_; 119 | outputAmt_ = _omega < _psi ? -(_inputAmt + _omega - _psi) : -(_inputAmt + _lambda.mul(_omega - _psi)); 120 | // outputAmt_ = _omega < _psi ? -(_inputAmt + _omega - _psi) : -(_inputAmt +_omega - _psi); 121 | } 122 | 123 | if (outputAmt_ / 1e13 == prevAmount / 1e13) { 124 | _nGLiq = _oGLiq + _inputAmt + outputAmt_; 125 | 126 | _nBals[_outputIndex] = _oBals[_outputIndex] + outputAmt_; 127 | 128 | enforceHalts(curve, _oGLiq, _nGLiq, _oBals, _nBals, _weights); 129 | 130 | enforceSwapInvariant(_oGLiq, _omega, _nGLiq, _psi); 131 | return outputAmt_; 132 | } else { 133 | _nGLiq = _oGLiq + _inputAmt + outputAmt_; 134 | 135 | _nBals[_outputIndex] = _oBals[_outputIndex].add(outputAmt_); 136 | } 137 | } 138 | 139 | revert("Curve/swap-convergence-failed"); 140 | } 141 | 142 | function calculateLiquidityMembrane( 143 | Storage.Curve storage curve, 144 | int128 _oGLiq, 145 | int128 _nGLiq, 146 | int128[] memory _oBals, 147 | int128[] memory _nBals 148 | ) internal view returns (int128 curves_) { 149 | enforceHalts(curve, _oGLiq, _nGLiq, _oBals, _nBals, curve.weights); 150 | 151 | int128 _omega; 152 | int128 _psi; 153 | 154 | { 155 | int128 _beta = curve.beta; 156 | int128 _delta = curve.delta; 157 | int128[] memory _weights = curve.weights; 158 | 159 | _omega = calculateFee(_oGLiq, _oBals, _beta, _delta, _weights); 160 | _psi = calculateFee(_nGLiq, _nBals, _beta, _delta, _weights); 161 | } 162 | 163 | int128 _feeDiff = _psi.sub(_omega); 164 | int128 _liqDiff = _nGLiq.sub(_oGLiq); 165 | int128 _oUtil = _oGLiq.sub(_omega); 166 | int128 _totalShells = curve.totalSupply.divu(1e18); 167 | int128 _curveMultiplier; 168 | 169 | if (_totalShells == 0) { 170 | curves_ = _nGLiq.sub(_psi); 171 | } else if (_feeDiff >= 0) { 172 | _curveMultiplier = _liqDiff.sub(_feeDiff).div(_oUtil); 173 | } else { 174 | _curveMultiplier = _liqDiff.sub(curve.lambda.mul(_feeDiff)); 175 | 176 | _curveMultiplier = _curveMultiplier.div(_oUtil); 177 | } 178 | 179 | if (_totalShells != 0) { 180 | curves_ = _totalShells.mul(_curveMultiplier); 181 | } 182 | } 183 | 184 | function enforceSwapInvariant( 185 | int128 _oGLiq, 186 | int128 _omega, 187 | int128 _nGLiq, 188 | int128 _psi 189 | ) private pure { 190 | int128 _nextUtil = _nGLiq - _psi; 191 | 192 | int128 _prevUtil = _oGLiq - _omega; 193 | 194 | int128 _diff = _nextUtil - _prevUtil; 195 | 196 | require(0 < _diff || _diff >= MAX_DIFF, "Curve/swap-invariant-violation"); 197 | } 198 | 199 | function enforceHalts( 200 | Storage.Curve storage curve, 201 | int128 _oGLiq, 202 | int128 _nGLiq, 203 | int128[] memory _oBals, 204 | int128[] memory _nBals, 205 | int128[] memory _weights 206 | ) private view { 207 | uint256 _length = _nBals.length; 208 | int128 _alpha = curve.alpha; 209 | 210 | for (uint256 i = 0; i < _length; i++) { 211 | int128 _nIdeal = _nGLiq.mul(_weights[i]); 212 | 213 | if (_nBals[i] > _nIdeal) { 214 | int128 _upperAlpha = ONE + _alpha; 215 | 216 | int128 _nHalt = _nIdeal.mul(_upperAlpha); 217 | 218 | if (_nBals[i] > _nHalt) { 219 | int128 _oHalt = _oGLiq.mul(_weights[i]).mul(_upperAlpha); 220 | 221 | if (_oBals[i] < _oHalt) revert("Curve/upper-halt"); 222 | if (_nBals[i] - _nHalt > _oBals[i] - _oHalt) revert("Curve/upper-halt"); 223 | } 224 | } else { 225 | int128 _lowerAlpha = ONE - _alpha; 226 | 227 | int128 _nHalt = _nIdeal.mul(_lowerAlpha); 228 | 229 | if (_nBals[i] < _nHalt) { 230 | int128 _oHalt = _oGLiq.mul(_weights[i]); 231 | _oHalt = _oHalt.mul(_lowerAlpha); 232 | 233 | if (_oBals[i] > _oHalt) revert("Curve/lower-halt"); 234 | if (_nHalt - _nBals[i] > _oHalt - _oBals[i]) revert("Curve/lower-halt"); 235 | } 236 | } 237 | } 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /src/Orchestrator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | pragma solidity ^0.8.13; 17 | 18 | import "../lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; 19 | import "../lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol"; 20 | 21 | import "./lib/ABDKMath64x64.sol"; 22 | 23 | import "./Storage.sol"; 24 | 25 | import "./CurveMath.sol"; 26 | 27 | library Orchestrator { 28 | using SafeERC20 for IERC20; 29 | using ABDKMath64x64 for int128; 30 | using ABDKMath64x64 for uint256; 31 | 32 | int128 private constant ONE_WEI = 0x12; 33 | 34 | event ParametersSet(uint256 alpha, uint256 beta, uint256 delta, uint256 epsilon, uint256 lambda); 35 | 36 | event AssetIncluded(address indexed numeraire, address indexed reserve, uint256 weight); 37 | 38 | event AssimilatorIncluded( 39 | address indexed derivative, 40 | address indexed numeraire, 41 | address indexed reserve, 42 | address assimilator 43 | ); 44 | 45 | function setParams( 46 | Storage.Curve storage curve, 47 | uint256 _alpha, 48 | uint256 _beta, 49 | uint256 _feeAtHalt, 50 | uint256 _epsilon, 51 | uint256 _lambda 52 | ) external { 53 | require(0 < _alpha && _alpha < 1e18, "Curve/parameter-invalid-alpha"); 54 | 55 | require(_beta < _alpha, "Curve/parameter-invalid-beta"); 56 | 57 | require(_feeAtHalt <= 5e17, "Curve/parameter-invalid-max"); 58 | 59 | require(_epsilon <= 1e16, "Curve/parameter-invalid-epsilon"); 60 | 61 | require(_lambda <= 1e18, "Curve/parameter-invalid-lambda"); 62 | 63 | int128 _omega = getFee(curve); 64 | 65 | curve.alpha = (_alpha + 1).divu(1e18); 66 | 67 | curve.beta = (_beta + 1).divu(1e18); 68 | 69 | curve.delta = (_feeAtHalt).divu(1e18).div(uint256(2).fromUInt().mul(curve.alpha.sub(curve.beta))) + ONE_WEI; 70 | 71 | curve.epsilon = (_epsilon + 1).divu(1e18); 72 | 73 | curve.lambda = (_lambda + 1).divu(1e18); 74 | 75 | int128 _psi = getFee(curve); 76 | 77 | require(_omega >= _psi, "Curve/parameters-increase-fee"); 78 | 79 | emit ParametersSet(_alpha, _beta, curve.delta.mulu(1e18), _epsilon, _lambda); 80 | } 81 | 82 | function setAssimilator( 83 | Storage.Curve storage curve, 84 | address _baseCurrency, 85 | address _baseAssim, 86 | address _quoteCurrency, 87 | address _quoteAssim 88 | ) external { 89 | require(_baseCurrency != address(0), "Curve/numeraire-cannot-be-zeroth-address"); 90 | require(_baseAssim != address(0), "Curve/numeraire-assimilator-cannot-be-zeroth-address"); 91 | require(_quoteCurrency != address(0), "Curve/reserve-cannot-be-zeroth-address"); 92 | require(_quoteAssim != address(0), "Curve/reserve-assimilator-cannot-be-zeroth-address"); 93 | 94 | Storage.Assimilator storage _baseAssimilator = curve.assimilators[_baseCurrency]; 95 | _baseAssimilator.addr = _baseAssim; 96 | 97 | Storage.Assimilator storage _quoteAssimilator = curve.assimilators[_quoteCurrency]; 98 | _quoteAssimilator.addr = _quoteAssim; 99 | 100 | curve.assets[0] = _baseAssimilator; 101 | curve.assets[1] = _quoteAssimilator; 102 | 103 | } 104 | 105 | function getFee(Storage.Curve storage curve) private view returns (int128 fee_) { 106 | int128 _gLiq; 107 | 108 | // Always pairs 109 | int128[] memory _bals = new int128[](2); 110 | 111 | for (uint256 i = 0; i < _bals.length; i++) { 112 | int128 _bal = Assimilators.viewNumeraireBalance(curve.assets[i].addr); 113 | 114 | _bals[i] = _bal; 115 | 116 | _gLiq += _bal; 117 | } 118 | 119 | fee_ = CurveMath.calculateFee(_gLiq, _bals, curve.beta, curve.delta, curve.weights); 120 | } 121 | 122 | function initialize( 123 | Storage.Curve storage curve, 124 | address[] storage numeraires, 125 | address[] storage reserves, 126 | address[] storage derivatives, 127 | address[] calldata _assets, 128 | uint256[] calldata _assetWeights 129 | ) external { 130 | require(_assetWeights.length == 2, "Curve/assetWeights-must-be-length-two"); 131 | require(_assets.length % 5 == 0, "Curve/assets-must-be-divisible-by-five"); 132 | 133 | for (uint256 i = 0; i < _assetWeights.length; i++) { 134 | uint256 ix = i * 5; 135 | 136 | numeraires.push(_assets[ix]); 137 | derivatives.push(_assets[ix]); 138 | 139 | reserves.push(_assets[2 + ix]); 140 | if (_assets[ix] != _assets[2 + ix]) derivatives.push(_assets[2 + ix]); 141 | 142 | includeAsset( 143 | curve, 144 | _assets[ix], // numeraire 145 | _assets[1 + ix], // numeraire assimilator 146 | _assets[2 + ix], // reserve 147 | _assets[3 + ix], // reserve assimilator 148 | _assets[4 + ix], // reserve approve to 149 | _assetWeights[i] 150 | ); 151 | } 152 | } 153 | 154 | function includeAsset( 155 | Storage.Curve storage curve, 156 | address _numeraire, 157 | address _numeraireAssim, 158 | address _reserve, 159 | address _reserveAssim, 160 | address _reserveApproveTo, 161 | uint256 _weight 162 | ) private { 163 | require(_numeraire != address(0), "Curve/numeraire-cannot-be-zeroth-address"); 164 | 165 | require(_numeraireAssim != address(0), "Curve/numeraire-assimilator-cannot-be-zeroth-address"); 166 | 167 | require(_reserve != address(0), "Curve/reserve-cannot-be-zeroth-address"); 168 | 169 | require(_reserveAssim != address(0), "Curve/reserve-assimilator-cannot-be-zeroth-address"); 170 | 171 | require(_weight < 1e18, "Curve/weight-must-be-less-than-one"); 172 | 173 | if (_numeraire != _reserve) IERC20(_numeraire).safeApprove(_reserveApproveTo, type(uint).max); 174 | 175 | Storage.Assimilator storage _numeraireAssimilator = curve.assimilators[_numeraire]; 176 | 177 | _numeraireAssimilator.addr = _numeraireAssim; 178 | 179 | _numeraireAssimilator.ix = uint8(curve.assets.length); 180 | 181 | Storage.Assimilator storage _reserveAssimilator = curve.assimilators[_reserve]; 182 | 183 | _reserveAssimilator.addr = _reserveAssim; 184 | 185 | _reserveAssimilator.ix = uint8(curve.assets.length); 186 | 187 | int128 __weight = _weight.divu(1e18).add(uint256(1).divu(1e18)); 188 | 189 | curve.weights.push(__weight); 190 | 191 | curve.assets.push(_numeraireAssimilator); 192 | 193 | emit AssetIncluded(_numeraire, _reserve, _weight); 194 | 195 | emit AssimilatorIncluded(_numeraire, _numeraire, _reserve, _numeraireAssim); 196 | 197 | if (_numeraireAssim != _reserveAssim) { 198 | emit AssimilatorIncluded(_reserve, _numeraire, _reserve, _reserveAssim); 199 | } 200 | } 201 | 202 | function viewCurve(Storage.Curve storage curve) 203 | external 204 | view 205 | returns ( 206 | uint256 alpha_, 207 | uint256 beta_, 208 | uint256 delta_, 209 | uint256 epsilon_, 210 | uint256 lambda_ 211 | ) 212 | { 213 | alpha_ = curve.alpha.mulu(1e18); 214 | 215 | beta_ = curve.beta.mulu(1e18); 216 | 217 | delta_ = curve.delta.mulu(1e18); 218 | 219 | epsilon_ = curve.epsilon.mulu(1e18); 220 | 221 | lambda_ = curve.lambda.mulu(1e18); 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /src/ProportionalLiquidity.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.13; 4 | 5 | import "./Assimilators.sol"; 6 | 7 | import "./Storage.sol"; 8 | 9 | import "./lib/UnsafeMath64x64.sol"; 10 | import "./lib/ABDKMath64x64.sol"; 11 | 12 | import "./CurveMath.sol"; 13 | import "./Structs.sol"; 14 | 15 | library ProportionalLiquidity { 16 | using ABDKMath64x64 for uint256; 17 | using ABDKMath64x64 for int128; 18 | using UnsafeMath64x64 for int128; 19 | 20 | event Transfer(address indexed from, address indexed to, uint256 value); 21 | 22 | int128 public constant ONE = 0x10000000000000000; 23 | int128 public constant ONE_WEI = 0x12; 24 | 25 | function proportionalDeposit(Storage.Curve storage curve, DepositData memory depositData) 26 | external 27 | returns (uint256 curves_, uint256[] memory) 28 | { 29 | int128 __deposit = depositData.deposits.divu(1e18); 30 | 31 | uint256 _length = curve.assets.length; 32 | 33 | uint256[] memory deposits_ = new uint256[](_length); 34 | 35 | (int128 _oGLiq, int128[] memory _oBals) = getGrossLiquidityAndBalancesForDeposit(curve); 36 | 37 | // Needed to calculate liquidity invariant 38 | // (int128 _oGLiqProp, int128[] memory _oBalsProp) = getGrossLiquidityAndBalances(curve); 39 | 40 | // No liquidity, oracle sets the ratio 41 | if (_oGLiq == 0) { 42 | for (uint256 i = 0; i < _length; i++) { 43 | // Variable here to avoid stack-too-deep errors 44 | int128 _d = __deposit.mul(curve.weights[i]); 45 | deposits_[i] = Assimilators.intakeNumeraire(curve.assets[i].addr, _d.add(ONE_WEI)); 46 | } 47 | } else { 48 | // We already have an existing pool ratio 49 | // which must be respected 50 | int128 _multiplier = __deposit.div(_oGLiq); 51 | 52 | uint256 _baseWeight = curve.weights[0].mulu(1e18); 53 | uint256 _quoteWeight = curve.weights[1].mulu(1e18); 54 | 55 | for (uint256 i = 0; i < _length; i++) { 56 | IntakeNumLpRatioInfo memory info; 57 | info.baseWeight = _baseWeight; 58 | info.minBase = depositData.minBase; 59 | info.maxBase = depositData.maxBase; 60 | info.quoteWeight = _quoteWeight; 61 | info.minQuote = depositData.minQuote; 62 | info.maxQuote = depositData.maxQuote; 63 | info.amount = _oBals[i].mul(_multiplier).add(ONE_WEI); 64 | deposits_[i] = Assimilators.intakeNumeraireLPRatio( 65 | curve.assets[i].addr, 66 | info 67 | ); 68 | } 69 | } 70 | 71 | int128 _totalShells = curve.totalSupply.divu(1e18); 72 | 73 | int128 _newShells = __deposit; 74 | 75 | if (_totalShells > 0) { 76 | _newShells = __deposit.mul(_totalShells); 77 | _newShells = _newShells.div(_oGLiq); 78 | } 79 | 80 | require(_newShells > 0, "Proportional Liquidity/can't mint negative amount"); 81 | mint(curve, msg.sender, curves_ = _newShells.mulu(1e18)); 82 | 83 | return (curves_, deposits_); 84 | } 85 | 86 | function viewProportionalDeposit(Storage.Curve storage curve, uint256 _deposit) 87 | external 88 | view 89 | returns (uint256 curves_, uint256[] memory) 90 | { 91 | int128 __deposit = _deposit.divu(1e18); 92 | 93 | uint256 _length = curve.assets.length; 94 | 95 | (int128 _oGLiq, int128[] memory _oBals) = getGrossLiquidityAndBalancesForDeposit(curve); 96 | 97 | uint256[] memory deposits_ = new uint256[](_length); 98 | 99 | // No liquidity 100 | if (_oGLiq == 0) { 101 | for (uint256 i = 0; i < _length; i++) { 102 | deposits_[i] = Assimilators.viewRawAmount( 103 | curve.assets[i].addr, 104 | __deposit.mul(curve.weights[i]).add(ONE_WEI) 105 | ); 106 | } 107 | } else { 108 | // We already have an existing pool ratio 109 | // this must be respected 110 | int128 _multiplier = __deposit.div(_oGLiq); 111 | 112 | uint256 _baseWeight = curve.weights[0].mulu(1e18); 113 | uint256 _quoteWeight = curve.weights[1].mulu(1e18); 114 | 115 | // Deposits into the pool is determined by existing LP ratio 116 | for (uint256 i = 0; i < _length; i++) { 117 | deposits_[i] = Assimilators.viewRawAmountLPRatio( 118 | curve.assets[i].addr, 119 | _baseWeight, 120 | _quoteWeight, 121 | _oBals[i].mul(_multiplier).add(ONE_WEI) 122 | ); 123 | } 124 | } 125 | 126 | int128 _totalShells = curve.totalSupply.divu(1e18); 127 | 128 | int128 _newShells = __deposit; 129 | 130 | if (_totalShells > 0) { 131 | _newShells = __deposit.mul(_totalShells); 132 | _newShells = _newShells.div(_oGLiq); 133 | } 134 | 135 | curves_ = _newShells.mulu(1e18); 136 | 137 | return (curves_, deposits_); 138 | } 139 | 140 | function proportionalWithdraw(Storage.Curve storage curve, uint256 _withdrawal) 141 | external 142 | returns (uint256[] memory) 143 | { 144 | uint256 _length = curve.assets.length; 145 | 146 | (, int128[] memory _oBals) = getGrossLiquidityAndBalances(curve); 147 | 148 | uint256[] memory withdrawals_ = new uint256[](_length); 149 | 150 | int128 _totalShells = curve.totalSupply.divu(1e18); 151 | int128 __withdrawal = _withdrawal.divu(1e18); 152 | 153 | int128 _multiplier = __withdrawal.div(_totalShells); 154 | 155 | for (uint256 i = 0; i < _length; i++) { 156 | withdrawals_[i] = Assimilators.outputNumeraire( 157 | curve.assets[i].addr, 158 | msg.sender, 159 | _oBals[i].mul(_multiplier) 160 | ); 161 | } 162 | 163 | burn(curve, msg.sender, _withdrawal); 164 | 165 | return withdrawals_; 166 | } 167 | 168 | function viewProportionalWithdraw(Storage.Curve storage curve, uint256 _withdrawal) 169 | external 170 | view 171 | returns (uint256[] memory) 172 | { 173 | uint256 _length = curve.assets.length; 174 | 175 | (, int128[] memory _oBals) = getGrossLiquidityAndBalances(curve); 176 | 177 | uint256[] memory withdrawals_ = new uint256[](_length); 178 | 179 | int128 _multiplier = _withdrawal.divu(1e18).div(curve.totalSupply.divu(1e18)); 180 | 181 | for (uint256 i = 0; i < _length; i++) { 182 | withdrawals_[i] = Assimilators.viewRawAmount(curve.assets[i].addr, _oBals[i].mul(_multiplier)); 183 | } 184 | 185 | return withdrawals_; 186 | } 187 | 188 | function getGrossLiquidityAndBalancesForDeposit(Storage.Curve storage curve) 189 | internal 190 | view 191 | returns (int128 grossLiquidity_, int128[] memory) 192 | { 193 | uint256 _length = curve.assets.length; 194 | 195 | int128[] memory balances_ = new int128[](_length); 196 | uint256 _baseWeight = curve.weights[0].mulu(1e18); 197 | uint256 _quoteWeight = curve.weights[1].mulu(1e18); 198 | 199 | for (uint256 i = 0; i < _length; i++) { 200 | int128 _bal = Assimilators.viewNumeraireBalanceLPRatio(_baseWeight, _quoteWeight, curve.assets[i].addr); 201 | 202 | balances_[i] = _bal; 203 | grossLiquidity_ += _bal; 204 | } 205 | 206 | return (grossLiquidity_, balances_); 207 | } 208 | 209 | function getGrossLiquidityAndBalances(Storage.Curve storage curve) 210 | internal 211 | view 212 | returns (int128 grossLiquidity_, int128[] memory) 213 | { 214 | uint256 _length = curve.assets.length; 215 | 216 | int128[] memory balances_ = new int128[](_length); 217 | 218 | for (uint256 i = 0; i < _length; i++) { 219 | int128 _bal = Assimilators.viewNumeraireBalance(curve.assets[i].addr); 220 | 221 | balances_[i] = _bal; 222 | grossLiquidity_ += _bal; 223 | } 224 | 225 | return (grossLiquidity_, balances_); 226 | } 227 | 228 | function burn( 229 | Storage.Curve storage curve, 230 | address account, 231 | uint256 amount 232 | ) private { 233 | curve.balances[account] = burnSub(curve.balances[account], amount); 234 | 235 | curve.totalSupply = burnSub(curve.totalSupply, amount); 236 | 237 | emit Transfer(msg.sender, address(0), amount); 238 | } 239 | 240 | function mint( 241 | Storage.Curve storage curve, 242 | address account, 243 | uint256 amount 244 | ) private { 245 | uint256 minLock = 1e6; 246 | if (curve.totalSupply == 0) { 247 | require(amount > minLock, "Proportional Liquidity/amount too small!"); 248 | uint256 toMintAmt = amount - minLock; 249 | // mint to lp provider 250 | curve.totalSupply = mintAdd(curve.totalSupply, toMintAmt); 251 | curve.balances[account] = mintAdd( 252 | curve.balances[account], 253 | toMintAmt 254 | ); 255 | emit Transfer(address(0), msg.sender, toMintAmt); 256 | // mint to 0 address 257 | curve.totalSupply = mintAdd(curve.totalSupply, minLock); 258 | curve.balances[address(0)] = mintAdd( 259 | curve.balances[address(0)], 260 | minLock 261 | ); 262 | emit Transfer(address(this), address(0), minLock); 263 | } else { 264 | curve.totalSupply = mintAdd(curve.totalSupply, amount); 265 | curve.balances[account] = mintAdd(curve.balances[account], amount); 266 | emit Transfer(address(0), msg.sender, amount); 267 | } 268 | } 269 | 270 | function mintAdd(uint256 x, uint256 y) private pure returns (uint256 z) { 271 | require((z = x + y) >= x, "Curve/mint-overflow"); 272 | } 273 | 274 | function burnSub(uint256 x, uint256 y) private pure returns (uint256 z) { 275 | require((z = x - y) <= x, "Curve/burn-underflow"); 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /src/Router.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | pragma solidity ^0.8.13; 17 | 18 | import "./CurveFactoryV2.sol"; 19 | import "./Curve.sol"; 20 | 21 | import "../lib/openzeppelin-contracts/contracts/utils/math/SafeMath.sol"; 22 | 23 | import "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; 24 | import "../lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol"; 25 | 26 | // Simplistic router that assumes USD is the only quote currency for 27 | contract Router { 28 | using SafeMath for uint256; 29 | using SafeERC20 for IERC20; 30 | 31 | address public factory; 32 | 33 | constructor(address _factory) { 34 | require(_factory != address(0), "Curve/factory-cannot-be-zeroth-address"); 35 | 36 | factory = _factory; 37 | } 38 | 39 | /// @notice view how much target amount a fixed origin amount will swap for 40 | /// @param _quoteCurrency the address of the quote currency (usually USDC) 41 | /// @param _origin the address of the origin 42 | /// @param _target the address of the target 43 | /// @param _originAmount the origin amount 44 | /// @return targetAmount_ the amount of target that will be returned 45 | function viewOriginSwap( 46 | address _quoteCurrency, 47 | address _origin, 48 | address _target, 49 | uint256 _originAmount 50 | ) external view returns (uint256 targetAmount_) { 51 | // If its an immediate pair then just swap directly on it 52 | address curve0 = CurveFactoryV2(factory).curves(keccak256(abi.encode(_origin, _target))); 53 | if (_origin == _quoteCurrency) { 54 | curve0 = CurveFactoryV2(factory).curves(keccak256(abi.encode(_target, _origin))); 55 | } 56 | if (curve0 != address(0)) { 57 | targetAmount_ = Curve(curve0).viewOriginSwap(_origin, _target, _originAmount); 58 | return targetAmount_; 59 | } 60 | 61 | // Otherwise go through the quote currency 62 | curve0 = CurveFactoryV2(factory).curves(keccak256(abi.encode(_origin, _quoteCurrency))); 63 | address curve1 = CurveFactoryV2(factory).curves(keccak256(abi.encode(_target, _quoteCurrency))); 64 | if (curve0 != address(0) && curve1 != address(0)) { 65 | uint256 _quoteAmount = Curve(curve0).viewOriginSwap(_origin, _quoteCurrency, _originAmount); 66 | targetAmount_ = Curve(curve1).viewOriginSwap(_quoteCurrency, _target, _quoteAmount); 67 | return targetAmount_; 68 | } 69 | 70 | revert("Router/No-path"); 71 | } 72 | 73 | /// @notice swap a dynamic origin amount for a fixed target amount 74 | /// @param _quoteCurrency the address of the quote currency (usually USDC) 75 | /// @param _origin the address of the origin 76 | /// @param _target the address of the target 77 | /// @param _originAmount the origin amount 78 | /// @param _minTargetAmount the minimum target amount 79 | /// @param _deadline deadline in block number after which the trade will not execute 80 | /// @return targetAmount_ the amount of target that has been swapped for the origin amount 81 | function originSwap( 82 | address _quoteCurrency, 83 | address _origin, 84 | address _target, 85 | uint256 _originAmount, 86 | uint256 _minTargetAmount, 87 | uint256 _deadline 88 | ) public returns (uint256 targetAmount_) { 89 | IERC20(_origin).safeTransferFrom(msg.sender, address(this), _originAmount); 90 | 91 | // If its an immediate pair then just swap directly on it 92 | address curve0 = CurveFactoryV2(factory).curves(keccak256(abi.encode(_origin, _target))); 93 | if (_origin == _quoteCurrency) { 94 | curve0 = CurveFactoryV2(factory).curves(keccak256(abi.encode(_target, _origin))); 95 | } 96 | if (curve0 != address(0)) { 97 | IERC20(_origin).safeApprove(curve0, _originAmount); 98 | targetAmount_ = Curve(curve0).originSwap(_origin, _target, _originAmount, _minTargetAmount, _deadline); 99 | IERC20(_target).safeTransfer(msg.sender, targetAmount_); 100 | return targetAmount_; 101 | } 102 | 103 | // Otherwise go through the quote currency 104 | curve0 = CurveFactoryV2(factory).curves(keccak256(abi.encode(_origin, _quoteCurrency))); 105 | address curve1 = CurveFactoryV2(factory).curves(keccak256(abi.encode(_target, _quoteCurrency))); 106 | if (curve0 != address(0) && curve1 != address(0)) { 107 | IERC20(_origin).safeApprove(curve0, _originAmount); 108 | uint256 _quoteAmount = Curve(curve0).originSwap(_origin, _quoteCurrency, _originAmount, 0, _deadline); 109 | 110 | IERC20(_quoteCurrency).safeApprove(curve1, _quoteAmount); 111 | targetAmount_ = Curve(curve1).originSwap( 112 | _quoteCurrency, 113 | _target, 114 | _quoteAmount, 115 | _minTargetAmount, 116 | _deadline 117 | ); 118 | IERC20(_target).safeTransfer(msg.sender, targetAmount_); 119 | return targetAmount_; 120 | } 121 | 122 | revert("Router/No-path"); 123 | } 124 | 125 | /// @notice view how much of the origin currency the target currency will take 126 | /// @param _quoteCurrency the address of the quote currency (usually USDC) 127 | /// @param _origin the address of the origin 128 | /// @param _target the address of the target 129 | /// @param _targetAmount the target amount 130 | /// @return originAmount_ the amount of target that has been swapped for the origin 131 | function viewTargetSwap( 132 | address _quoteCurrency, 133 | address _origin, 134 | address _target, 135 | uint256 _targetAmount 136 | ) public view returns (uint256 originAmount_) { 137 | // If its an immediate pair then just swap directly on it 138 | address curve0 = CurveFactoryV2(factory).curves(keccak256(abi.encode(_origin, _target))); 139 | if (_origin == _quoteCurrency) { 140 | curve0 = CurveFactoryV2(factory).curves(keccak256(abi.encode(_target, _origin))); 141 | } 142 | 143 | if (curve0 != address(0)) { 144 | originAmount_ = Curve(curve0).viewTargetSwap(_origin, _target, _targetAmount); 145 | return originAmount_; 146 | } 147 | 148 | // Otherwise go through the quote currency 149 | curve0 = CurveFactoryV2(factory).curves(keccak256(abi.encode(_target, _quoteCurrency))); 150 | address curve1 = CurveFactoryV2(factory).curves(keccak256(abi.encode(_origin, _quoteCurrency))); 151 | if (curve0 != address(0) && curve1 != address(0)) { 152 | uint256 _quoteAmount = Curve(curve0).viewTargetSwap(_quoteCurrency, _target, _targetAmount); 153 | originAmount_ = Curve(curve1).viewTargetSwap(_origin, _quoteCurrency, _quoteAmount); 154 | return originAmount_; 155 | } 156 | 157 | revert("Router/No-path"); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/Storage.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | pragma solidity ^0.8.13; 17 | 18 | import "./interfaces/IOracle.sol"; 19 | import "./Assimilators.sol"; 20 | 21 | contract Storage { 22 | struct Curve { 23 | // Curve parameters 24 | int128 alpha; 25 | int128 beta; 26 | int128 delta; 27 | int128 epsilon; 28 | int128 lambda; 29 | int128[] weights; 30 | // Assets and their assimilators 31 | Assimilator[] assets; 32 | mapping(address => Assimilator) assimilators; 33 | // Oracles to determine the price 34 | // Note that 0'th index should always be USDC 1e18 35 | // Oracle's pricing should be denominated in Currency/USDC 36 | mapping(address => IOracle) oracles; 37 | // ERC20 Interface 38 | uint256 totalSupply; 39 | mapping(address => uint256) balances; 40 | mapping(address => mapping(address => uint256)) allowances; 41 | } 42 | 43 | struct Assimilator { 44 | address addr; 45 | uint8 ix; 46 | } 47 | 48 | // Curve parameters 49 | Curve public curve; 50 | 51 | // Ownable 52 | address public owner; 53 | 54 | string public name; 55 | string public symbol; 56 | uint8 public constant decimals = 18; 57 | 58 | address[] public derivatives; 59 | address[] public numeraires; 60 | address[] public reserves; 61 | 62 | // Curve operational state 63 | bool public frozen = false; 64 | bool public emergency = false; 65 | bool internal notEntered = true; 66 | } 67 | -------------------------------------------------------------------------------- /src/Structs.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import "./interfaces/ICurveFactory.sol"; 5 | import "./interfaces/IOracle.sol"; 6 | 7 | struct OriginSwapData { 8 | address _origin; 9 | address _target; 10 | uint256 _originAmount; 11 | address _recipient; 12 | address _curveFactory; 13 | } 14 | 15 | struct TargetSwapData { 16 | address _origin; 17 | address _target; 18 | uint256 _targetAmount; 19 | address _recipient; 20 | address _curveFactory; 21 | } 22 | 23 | struct SwapInfo { 24 | int128 totalAmount; 25 | int128 totalFee; 26 | int128 amountToUser; 27 | int128 amountToTreasury; 28 | int128 protocolFeePercentage; 29 | address treasury; 30 | ICurveFactory curveFactory; 31 | } 32 | 33 | struct CurveInfo { 34 | string _name; 35 | string _symbol; 36 | address _baseCurrency; 37 | address _quoteCurrency; 38 | uint256 _baseWeight; 39 | uint256 _quoteWeight; 40 | IOracle _baseOracle; 41 | IOracle _quoteOracle; 42 | uint256 _alpha; 43 | uint256 _beta; 44 | uint256 _feeAtHalt; 45 | uint256 _epsilon; 46 | uint256 _lambda; 47 | } 48 | 49 | struct DepositData { 50 | uint256 deposits; 51 | uint256 minQuote; 52 | uint256 minBase; 53 | uint256 maxQuote; 54 | uint256 maxBase; 55 | } 56 | 57 | struct IntakeNumLpRatioInfo { 58 | uint256 baseWeight; 59 | uint256 minBase; 60 | uint256 maxBase; 61 | uint256 quoteWeight; 62 | uint256 minQuote; 63 | uint256 maxQuote; 64 | int128 amount; 65 | } -------------------------------------------------------------------------------- /src/ViewLiquidity.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | pragma solidity ^0.8.13; 17 | 18 | import "./Storage.sol"; 19 | 20 | import "./Assimilators.sol"; 21 | 22 | import "./lib/ABDKMath64x64.sol"; 23 | 24 | library ViewLiquidity { 25 | using ABDKMath64x64 for int128; 26 | 27 | function viewLiquidity(Storage.Curve storage curve) 28 | external 29 | view 30 | returns (uint256 total_, uint256[] memory individual_) 31 | { 32 | uint256 _length = curve.assets.length; 33 | 34 | individual_ = new uint256[](_length); 35 | 36 | for (uint256 i = 0; i < _length; i++) { 37 | uint256 _liquidity = Assimilators.viewNumeraireBalance(curve.assets[i].addr).mulu(1e18); 38 | 39 | total_ += _liquidity; 40 | individual_[i] = _liquidity; 41 | } 42 | 43 | return (total_, individual_); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/assimilators/AssimilatorV2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | pragma solidity ^0.8.13; 17 | 18 | import "../../lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol"; 19 | import "../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; 20 | import "../../lib/openzeppelin-contracts/contracts/utils/math/SafeMath.sol"; 21 | import "../../lib/openzeppelin-contracts/contracts/security/ReentrancyGuard.sol"; 22 | 23 | import "../lib/ABDKMath64x64.sol"; 24 | import "../interfaces/IAssimilator.sol"; 25 | import "../interfaces/IOracle.sol"; 26 | 27 | contract AssimilatorV2 is IAssimilator, ReentrancyGuard { 28 | using ABDKMath64x64 for int128; 29 | using ABDKMath64x64 for uint256; 30 | 31 | using SafeMath for uint256; 32 | using SafeERC20 for IERC20; 33 | 34 | IERC20 public immutable usdc; 35 | 36 | IOracle public immutable oracle; 37 | IERC20 public immutable token; 38 | uint256 public immutable oracleDecimals; 39 | uint256 public immutable tokenDecimals; 40 | 41 | // solhint-disable-next-line 42 | constructor( 43 | IOracle _oracle, 44 | address _token, 45 | uint256 _tokenDecimals, 46 | uint256 _oracleDecimals 47 | ) { 48 | oracle = _oracle; 49 | token = IERC20(_token); 50 | oracleDecimals = _oracleDecimals; 51 | tokenDecimals = _tokenDecimals; 52 | usdc = IERC20(quoteAddress()); 53 | } 54 | 55 | function quoteAddress () internal view returns (address) { 56 | uint256 chainID; 57 | assembly { 58 | chainID := chainid() 59 | } 60 | if(chainID == 1) { 61 | return 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; 62 | }else if (chainID == 137) { 63 | return 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174; 64 | } else if (chainID == 42161) { 65 | return 0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8; 66 | }else{ 67 | return address(0); 68 | } 69 | } 70 | 71 | function getRate() public view override returns (uint256) { 72 | (, int256 price, , , ) = oracle.latestRoundData(); 73 | return uint256(price); 74 | } 75 | 76 | // takes raw eurs amount, transfers it in, calculates corresponding numeraire amount and returns it 77 | function intakeRawAndGetBalance(uint256 _amount) 78 | external 79 | override 80 | returns (int128 amount_, int128 balance_) 81 | { 82 | token.safeTransferFrom(msg.sender, address(this), _amount); 83 | 84 | uint256 _balance = token.balanceOf(address(this)); 85 | 86 | uint256 _rate = getRate(); 87 | 88 | balance_ = ((_balance * _rate) / 10**oracleDecimals).divu(10**tokenDecimals); 89 | 90 | amount_ = ((_amount * _rate) / 10**oracleDecimals).divu( 91 | 10**tokenDecimals 92 | ); 93 | } 94 | 95 | // takes raw eurs amount, transfers it in, calculates corresponding numeraire amount and returns it 96 | function intakeRaw(uint256 _amount) 97 | external 98 | override 99 | returns (int128 amount_) 100 | { 101 | token.safeTransferFrom(msg.sender, address(this), _amount); 102 | 103 | uint256 _rate = getRate(); 104 | 105 | amount_ = ((_amount * _rate) / 10**oracleDecimals).divu( 106 | 10**tokenDecimals 107 | ); 108 | } 109 | 110 | // takes a numeraire amount, calculates the raw amount of eurs, transfers it in and returns the corresponding raw amount 111 | function intakeNumeraire(int128 _amount) 112 | external 113 | override 114 | returns (uint256 amount_) 115 | { 116 | uint256 _rate = getRate(); 117 | 118 | amount_ = (_amount.mulu(10**tokenDecimals) * 10**oracleDecimals) / _rate; 119 | 120 | token.safeTransferFrom(msg.sender, address(this), amount_); 121 | } 122 | 123 | // takes a numeraire amount, calculates the raw amount of eurs, transfers it in and returns the corresponding raw amount 124 | function intakeNumeraireLPRatio( 125 | uint256 _baseWeight, 126 | uint256 _minBaseAmount, 127 | uint256 _maxBaseAmount, 128 | uint256 _quoteWeight, 129 | uint256 _minQuoteAmount, 130 | uint256 _maxQuoteAmount, 131 | address _addr, 132 | int128 _amount 133 | ) external override returns (uint256 amount_) { 134 | uint256 _tokenBal = token.balanceOf(_addr); 135 | 136 | if (_tokenBal <= 0) return 0; 137 | 138 | _tokenBal = _tokenBal.mul(1e18).div(_baseWeight); 139 | 140 | uint256 _usdcBal = usdc.balanceOf(_addr).mul(1e18).div(_quoteWeight); 141 | 142 | // Rate is in 1e6 143 | uint256 _rate = _usdcBal.mul(10**tokenDecimals).div(_tokenBal); 144 | 145 | amount_ = (_amount.mulu(10**tokenDecimals) * 1e6) / _rate; 146 | 147 | if (address(token) == address(usdc)) { 148 | require(amount_ >= _minQuoteAmount && amount_ <= _maxQuoteAmount, "Assimilator/LP Ratio imbalanced!"); 149 | } else { 150 | require(amount_ >= _minBaseAmount && amount_ <= _maxBaseAmount, "Assimilator/LP Ratio imbalanced!"); 151 | } 152 | token.safeTransferFrom(msg.sender, address(this), amount_); 153 | } 154 | 155 | // takes a raw amount of eurs and transfers it out, returns numeraire value of the raw amount 156 | function outputRawAndGetBalance(address _dst, uint256 _amount) external override returns (int128 amount_, int128 balance_){ 157 | uint256 _rate = getRate(); 158 | 159 | token.safeTransfer(_dst, _amount); 160 | 161 | uint256 _balance = token.balanceOf(address(this)); 162 | 163 | amount_ = ((_amount * _rate) / 10**oracleDecimals).divu(10**tokenDecimals); 164 | 165 | balance_ = ((_balance * _rate) / 10**oracleDecimals).divu( 166 | 10**tokenDecimals 167 | ); 168 | } 169 | 170 | // takes a raw amount of eurs and transfers it out, returns numeraire value of the raw amount 171 | function outputRaw(address _dst, uint256 _amount) 172 | external 173 | override 174 | returns (int128 amount_) 175 | { 176 | uint256 _rate = getRate(); 177 | 178 | token.safeTransfer(_dst, _amount); 179 | 180 | amount_ = ((_amount * _rate) / 10**oracleDecimals).divu(10**tokenDecimals); 181 | } 182 | 183 | // takes a numeraire value of eurs, figures out the raw amount, transfers raw amount out, and returns raw amount 184 | function outputNumeraire(address _dst, int128 _amount) 185 | external 186 | override 187 | returns (uint256 amount_) 188 | { 189 | uint256 _rate = getRate(); 190 | 191 | amount_ = 192 | (_amount.mulu(10**tokenDecimals) * 10**oracleDecimals) / 193 | _rate; 194 | 195 | token.safeTransfer(_dst, amount_); 196 | } 197 | 198 | // takes a numeraire amount and returns the raw amount 199 | function viewRawAmount(int128 _amount) 200 | external 201 | view 202 | override 203 | returns (uint256 amount_) 204 | { 205 | uint256 _rate = getRate(); 206 | 207 | amount_ = 208 | (_amount.mulu(10**tokenDecimals) * 10**oracleDecimals) / 209 | _rate; 210 | } 211 | 212 | function viewRawAmountLPRatio( 213 | uint256 _baseWeight, 214 | uint256 _quoteWeight, 215 | address _addr, 216 | int128 _amount 217 | ) external view override returns (uint256 amount_) { 218 | uint256 _tokenBal = token.balanceOf(_addr); 219 | 220 | if (_tokenBal <= 0) return 0; 221 | 222 | // 1e2 223 | _tokenBal = _tokenBal.mul(1e18).div(_baseWeight); 224 | 225 | // 1e6 226 | uint256 _usdcBal = usdc.balanceOf(_addr).mul(1e18).div(_quoteWeight); 227 | 228 | // Rate is in 1e6 229 | uint256 _rate = _usdcBal.mul(10**tokenDecimals).div(_tokenBal); 230 | 231 | amount_ = (_amount.mulu(10**tokenDecimals) * 1e6) / _rate; 232 | } 233 | 234 | // takes a raw amount and returns the numeraire amount 235 | function viewNumeraireAmount(uint256 _amount) 236 | external 237 | view 238 | override 239 | returns (int128 amount_) 240 | { 241 | uint256 _rate = getRate(); 242 | 243 | amount_ = ((_amount * _rate) / 10**oracleDecimals).divu( 244 | 10**tokenDecimals 245 | ); 246 | } 247 | 248 | // views the numeraire value of the current balance of the reserve, in this case eurs 249 | function viewNumeraireBalance(address _addr) 250 | external 251 | view 252 | override 253 | returns (int128 balance_) 254 | { 255 | uint256 _rate = getRate(); 256 | 257 | uint256 _balance = token.balanceOf(_addr); 258 | 259 | if (_balance <= 0) return ABDKMath64x64.fromUInt(0); 260 | 261 | balance_ = ((_balance * _rate) / 10**oracleDecimals).divu( 262 | 10**tokenDecimals 263 | ); 264 | } 265 | 266 | // views the numeraire value of the current balance of the reserve, in this case eurs 267 | function viewNumeraireAmountAndBalance(address _addr, uint256 _amount) 268 | external 269 | view 270 | override 271 | returns (int128 amount_, int128 balance_) 272 | { 273 | uint256 _rate = getRate(); 274 | 275 | amount_ = ((_amount * _rate) / 10**oracleDecimals).divu( 276 | 10**tokenDecimals 277 | ); 278 | 279 | uint256 _balance = token.balanceOf(_addr); 280 | 281 | balance_ = ((_balance * _rate) / 10**oracleDecimals).divu( 282 | 10**tokenDecimals 283 | ); 284 | } 285 | 286 | // views the numeraire value of the current balance of the reserve, in this case eurs 287 | // instead of calculating with chainlink's "rate" it'll be determined by the existing 288 | // token ratio. This is in here to prevent LPs from losing out on future oracle price updates 289 | function viewNumeraireBalanceLPRatio( 290 | uint256 _baseWeight, 291 | uint256 _quoteWeight, 292 | address _addr 293 | ) external view override returns (int128 balance_) { 294 | uint256 _tokenBal = token.balanceOf(_addr); 295 | 296 | if (_tokenBal <= 0) return ABDKMath64x64.fromUInt(0); 297 | 298 | uint256 _usdcBal = usdc.balanceOf(_addr).mul(1e18).div(_quoteWeight); 299 | 300 | // Rate is in 1e6 301 | uint256 _rate = _usdcBal.mul(1e18).div( 302 | _tokenBal.mul(1e18).div(_baseWeight) 303 | ); 304 | 305 | balance_ = ((_tokenBal * _rate) / 1e6).divu(1e18); 306 | } 307 | 308 | function transferFee(int128 _amount, address _treasury) external override { 309 | uint256 _rate = getRate(); 310 | if (_amount < 0) _amount = -(_amount); 311 | uint256 amount = (_amount.mulu(10**tokenDecimals) * 312 | 10**oracleDecimals) / _rate; 313 | token.safeTransfer(_treasury, amount); 314 | } 315 | } 316 | -------------------------------------------------------------------------------- /src/interfaces/IAssimilator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | pragma solidity ^0.8.13; 17 | 18 | interface IAssimilator { 19 | function oracleDecimals() external view returns(uint256); 20 | 21 | function tokenDecimals() external view returns(uint256); 22 | 23 | function getRate() external view returns (uint256); 24 | 25 | function intakeRaw(uint256 amount) external returns (int128); 26 | 27 | function intakeRawAndGetBalance(uint256 amount) 28 | external 29 | returns (int128, int128); 30 | 31 | function intakeNumeraire(int128 amount) external returns (uint256); 32 | 33 | function intakeNumeraireLPRatio( 34 | uint256, 35 | uint256, 36 | uint256, 37 | uint256, 38 | uint256, 39 | uint256, 40 | address, 41 | int128 42 | ) external returns (uint256); 43 | 44 | function outputRaw(address dst, uint256 amount) external returns (int128); 45 | 46 | function outputRawAndGetBalance(address dst, uint256 amount) 47 | external 48 | returns (int128, int128); 49 | 50 | function outputNumeraire(address dst, int128 amount) 51 | external 52 | returns (uint256); 53 | 54 | function viewRawAmount(int128) external view returns (uint256); 55 | 56 | function viewRawAmountLPRatio( 57 | uint256, 58 | uint256, 59 | address, 60 | int128 61 | ) external view returns (uint256); 62 | 63 | function viewNumeraireAmount(uint256) external view returns (int128); 64 | 65 | function viewNumeraireBalanceLPRatio( 66 | uint256, 67 | uint256, 68 | address 69 | ) external view returns (int128); 70 | 71 | function viewNumeraireBalance(address) external view returns (int128); 72 | 73 | function viewNumeraireAmountAndBalance(address, uint256) 74 | external 75 | view 76 | returns (int128, int128); 77 | 78 | function transferFee(int128, address) external; 79 | } 80 | -------------------------------------------------------------------------------- /src/interfaces/IAssimilatorFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | pragma solidity ^0.8.13; 17 | 18 | import "../assimilators/AssimilatorV2.sol"; 19 | import "../interfaces/IOracle.sol"; 20 | 21 | interface IAssimilatorFactory { 22 | function getAssimilator(address _token) external view returns (AssimilatorV2); 23 | 24 | function newAssimilator( 25 | IOracle _oracle, 26 | address _token, 27 | uint256 _tokenDecimals 28 | ) external returns (AssimilatorV2); 29 | } 30 | -------------------------------------------------------------------------------- /src/interfaces/IConfig.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.13; 4 | 5 | interface IConfig { 6 | function getGlobalFrozenState() external view returns (bool); 7 | 8 | function getFlashableState() external view returns (bool); 9 | 10 | function getProtocolFee() external view returns (int128); 11 | 12 | function getProtocolTreasury() external view returns (address); 13 | 14 | function setGlobalFrozen(bool) external; 15 | 16 | function toggleGlobalGuarded () external; 17 | 18 | function setPoolGuarded (address, bool) external; 19 | 20 | function setGlobalGuardAmount (uint256) external; 21 | 22 | function setPoolCap (address, uint256) external; 23 | 24 | function setPoolGuardAmount (address, uint256) external; 25 | 26 | function isPoolGuarded (address) external view returns (bool); 27 | 28 | function getPoolGuardAmount (address) external view returns (uint256); 29 | 30 | function getPoolCap (address) external view returns (uint256); 31 | 32 | function setFlashable(bool) external; 33 | 34 | function updateProtocolTreasury(address) external; 35 | 36 | function updateProtocolFee(int128) external; 37 | 38 | } -------------------------------------------------------------------------------- /src/interfaces/ICurve.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | interface ICurve { 5 | function viewDeposit(uint256 deposit) external view returns (uint256, uint256[] memory); 6 | function deposit(uint256 deposit, uint256 deadline) external; 7 | function flash(address recipient, uint256 amount0, uint256 amount1, bytes calldata data) external; 8 | function derivatives(uint256) external view returns (address); 9 | } 10 | -------------------------------------------------------------------------------- /src/interfaces/ICurveFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | interface ICurveFactory { 5 | function getProtocolFee() external view returns (int128); 6 | function getProtocolTreasury() external view returns (address); 7 | function getGlobalFrozenState() external view returns (bool); 8 | function getFlashableState() external view returns (bool); 9 | function isPoolGuarded(address pool) external view returns (bool); 10 | function getPoolGuardAmount(address pool) external view returns (uint256); 11 | function getPoolCap(address pool) external view returns (uint256); 12 | } 13 | -------------------------------------------------------------------------------- /src/interfaces/IERC20Detailed.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | import "../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; 17 | 18 | pragma solidity ^0.8.7; 19 | 20 | interface IERC20Detailed is IERC20 { 21 | function name() external returns (string memory); 22 | 23 | function symbol() external returns (string memory); 24 | 25 | function decimals() external returns (uint8); 26 | 27 | function mint(address account, uint256 amount) external; 28 | } 29 | -------------------------------------------------------------------------------- /src/interfaces/IFlashCallback.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | interface IFlashCallback { 5 | function flashCallback( 6 | uint256 fee0, 7 | uint256 fee1, 8 | bytes calldata data 9 | ) external; 10 | } 11 | -------------------------------------------------------------------------------- /src/interfaces/IFreeFromUpTo.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | pragma solidity ^0.8.13; 17 | 18 | interface IFreeFromUpTo { 19 | function freeFromUpTo(address from, uint256 value) external returns (uint256 freed); 20 | } 21 | -------------------------------------------------------------------------------- /src/interfaces/IOracle.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | pragma solidity ^0.8.13; 17 | 18 | interface IOracle { 19 | function acceptOwnership() external; 20 | 21 | function accessController() external view returns (address); 22 | 23 | function aggregator() external view returns (address); 24 | 25 | function confirmAggregator(address _aggregator) external; 26 | 27 | function decimals() external view returns (uint8); 28 | 29 | function description() external view returns (string memory); 30 | 31 | function getAnswer(uint256 _roundId) external view returns (int256); 32 | 33 | function getRoundData(uint80 _roundId) 34 | external 35 | view 36 | returns ( 37 | uint80 roundId, 38 | int256 answer, 39 | uint256 startedAt, 40 | uint256 updatedAt, 41 | uint80 answeredInRound 42 | ); 43 | 44 | function getTimestamp(uint256 _roundId) external view returns (uint256); 45 | 46 | function latestAnswer() external view returns (int256); 47 | 48 | function latestRound() external view returns (uint256); 49 | 50 | function latestRoundData() 51 | external 52 | view 53 | returns ( 54 | uint80 roundId, 55 | int256 answer, 56 | uint256 startedAt, 57 | uint256 updatedAt, 58 | uint80 answeredInRound 59 | ); 60 | 61 | function latestTimestamp() external view returns (uint256); 62 | 63 | function owner() external view returns (address); 64 | 65 | function phaseAggregators(uint16) external view returns (address); 66 | 67 | function phaseId() external view returns (uint16); 68 | 69 | function proposeAggregator(address _aggregator) external; 70 | 71 | function proposedAggregator() external view returns (address); 72 | 73 | function proposedGetRoundData(uint80 _roundId) 74 | external 75 | view 76 | returns ( 77 | uint80 roundId, 78 | int256 answer, 79 | uint256 startedAt, 80 | uint256 updatedAt, 81 | uint80 answeredInRound 82 | ); 83 | 84 | function proposedLatestRoundData() 85 | external 86 | view 87 | returns ( 88 | uint80 roundId, 89 | int256 answer, 90 | uint256 startedAt, 91 | uint256 updatedAt, 92 | uint80 answeredInRound 93 | ); 94 | 95 | function setController(address _accessController) external; 96 | 97 | function transferOwnership(address _to) external; 98 | 99 | function version() external view returns (uint256); 100 | } 101 | -------------------------------------------------------------------------------- /src/lib/FullMath.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | /// @title Contains 512-bit math functions 5 | /// @notice Facilitates multiplication and division that can have overflow of an intermediate value without any loss of precision 6 | /// @dev Handles "phantom overflow" i.e., allows multiplication and division where an intermediate value overflows 256 bits 7 | library FullMath { 8 | /// @notice Calculates floor(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0 9 | /// @param a The multiplicand 10 | /// @param b The multiplier 11 | /// @param denominator The divisor 12 | /// @return result The 256-bit result 13 | /// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv 14 | function mulDiv( 15 | uint256 a, 16 | uint256 b, 17 | uint256 denominator 18 | ) internal pure returns (uint256 result) { 19 | // 512-bit multiply [prod1 prod0] = a * b 20 | // Compute the product mod 2**256 and mod 2**256 - 1 21 | // then use the Chinese Remainder Theorem to reconstruct 22 | // the 512 bit result. The result is stored in two 256 23 | // variables such that product = prod1 * 2**256 + prod0 24 | uint256 prod0; // Least significant 256 bits of the product 25 | uint256 prod1; // Most significant 256 bits of the product 26 | assembly { 27 | let mm := mulmod(a, b, not(0)) 28 | prod0 := mul(a, b) 29 | prod1 := sub(sub(mm, prod0), lt(mm, prod0)) 30 | } 31 | 32 | // Handle non-overflow cases, 256 by 256 division 33 | if (prod1 == 0) { 34 | require(denominator > 0); 35 | assembly { 36 | result := div(prod0, denominator) 37 | } 38 | return result; 39 | } 40 | 41 | // Make sure the result is less than 2**256. 42 | // Also prevents denominator == 0 43 | require(denominator > prod1); 44 | 45 | /////////////////////////////////////////////// 46 | // 512 by 256 division. 47 | /////////////////////////////////////////////// 48 | 49 | // Make division exact by subtracting the remainder from [prod1 prod0] 50 | // Compute remainder using mulmod 51 | uint256 remainder; 52 | assembly { 53 | remainder := mulmod(a, b, denominator) 54 | } 55 | // Subtract 256 bit number from 512 bit number 56 | assembly { 57 | prod1 := sub(prod1, gt(remainder, prod0)) 58 | prod0 := sub(prod0, remainder) 59 | } 60 | 61 | // Factor powers of two out of denominator 62 | // Compute largest power of two divisor of denominator. 63 | // Always >= 1. 64 | uint256 twos = denominator & (~denominator + 1); 65 | // Divide denominator by power of two 66 | assembly { 67 | denominator := div(denominator, twos) 68 | } 69 | 70 | // Divide [prod1 prod0] by the factors of two 71 | assembly { 72 | prod0 := div(prod0, twos) 73 | } 74 | // Shift in bits from prod1 into prod0. For this we need 75 | // to flip `twos` such that it is 2**256 / twos. 76 | // If twos is zero, then it becomes one 77 | assembly { 78 | twos := add(div(sub(0, twos), twos), 1) 79 | } 80 | prod0 |= prod1 * twos; 81 | 82 | // Invert denominator mod 2**256 83 | // Now that denominator is an odd number, it has an inverse 84 | // modulo 2**256 such that denominator * inv = 1 mod 2**256. 85 | // Compute the inverse by starting with a seed that is correct 86 | // correct for four bits. That is, denominator * inv = 1 mod 2**4 87 | uint256 inv = (3 * denominator) ^ 2; 88 | // Now use Newton-Raphson iteration to improve the precision. 89 | // Thanks to Hensel's lifting lemma, this also works in modular 90 | // arithmetic, doubling the correct bits in each step. 91 | inv *= 2 - denominator * inv; // inverse mod 2**8 92 | inv *= 2 - denominator * inv; // inverse mod 2**16 93 | inv *= 2 - denominator * inv; // inverse mod 2**32 94 | inv *= 2 - denominator * inv; // inverse mod 2**64 95 | inv *= 2 - denominator * inv; // inverse mod 2**128 96 | inv *= 2 - denominator * inv; // inverse mod 2**256 97 | 98 | // Because the division is now exact we can divide by multiplying 99 | // with the modular inverse of denominator. This will give us the 100 | // correct result modulo 2**256. Since the precoditions guarantee 101 | // that the outcome is less than 2**256, this is the final result. 102 | // We don't need to compute the high bits of the result and prod1 103 | // is no longer required. 104 | result = prod0 * inv; 105 | return result; 106 | } 107 | 108 | /// @notice Calculates ceil(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0 109 | /// @param a The multiplicand 110 | /// @param b The multiplier 111 | /// @param denominator The divisor 112 | /// @return result The 256-bit result 113 | function mulDivRoundingUp( 114 | uint256 a, 115 | uint256 b, 116 | uint256 denominator 117 | ) internal pure returns (uint256 result) { 118 | result = mulDiv(a, b, denominator); 119 | if (mulmod(a, b, denominator) > 0) { 120 | require(result < type(uint256).max); 121 | result++; 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/lib/NoDelegateCall.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.13; 3 | 4 | /// @title Prevents delegatecall to a contract 5 | /// @notice Base contract that provides a modifier for preventing delegatecall to methods in a child contract 6 | abstract contract NoDelegateCall { 7 | /// @dev The original address of this contract 8 | address private immutable original; 9 | 10 | constructor() { 11 | // Immutables are computed in the init code of the contract, and then inlined into the deployed bytecode. 12 | // In other words, this variable won't change when it's checked at runtime. 13 | original = address(this); 14 | } 15 | 16 | /// @dev Private method is used instead of inlining into modifier because modifiers are copied into each method, 17 | /// and the use of immutable means the address bytes are copied in every place the modifier is used. 18 | function checkNotDelegateCall() private view { 19 | require(address(this) == original); 20 | } 21 | 22 | /// @notice Prevents delegatecall into the modified method 23 | modifier noDelegateCall() { 24 | checkNotDelegateCall(); 25 | _; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/lib/UnsafeMath64x64.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.13; 4 | 5 | library UnsafeMath64x64 { 6 | 7 | /** 8 | * Calculate x * y rounding down. 9 | * 10 | * @param x signed 64.64-bit fixed point number 11 | * @param y signed 64.64-bit fixed point number 12 | * @return signed 64.64-bit fixed point number 13 | */ 14 | 15 | function us_mul (int128 x, int128 y) internal pure returns (int128) { 16 | int256 result = int256(x) * y >> 64; 17 | return int128 (result); 18 | } 19 | 20 | /** 21 | * Calculate x / y rounding towards zero. Revert on overflow or when y is 22 | * zero. 23 | * 24 | * @param x signed 64.64-bit fixed point number 25 | * @param y signed 64.64-bit fixed point number 26 | * @return signed 64.64-bit fixed point number 27 | */ 28 | 29 | function us_div (int128 x, int128 y) internal pure returns (int128) { 30 | int256 result = (int256 (x) << 64) / y; 31 | return int128 (result); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /test/Curve.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import "forge-std/Test.sol"; 5 | 6 | import "../src/AssimilatorFactory.sol"; 7 | import "../src/CurveFactoryV2.sol"; 8 | import "../src/Curve.sol"; 9 | import "../src/Config.sol"; 10 | import "../src/interfaces/IERC20Detailed.sol"; 11 | import "../src/interfaces/IAssimilator.sol"; 12 | import "../src/interfaces/IOracle.sol"; 13 | 14 | import "./lib/MockUser.sol"; 15 | import "./lib/CheatCodes.sol"; 16 | import "./lib/Address.sol"; 17 | import "./lib/CurveParams.sol"; 18 | 19 | contract CurveFactoryV2Test is Test { 20 | using SafeMath for uint256; 21 | 22 | CheatCodes cheats = CheatCodes(HEVM_ADDRESS); 23 | MockUser treasury; 24 | MockUser newTreasury; 25 | MockUser liquidityProvider; 26 | 27 | AssimilatorFactory assimilatorFactory; 28 | CurveFactoryV2 curveFactory; 29 | Config config; 30 | 31 | IERC20Detailed usdc = IERC20Detailed(Mainnet.USDC); 32 | IERC20Detailed cadc = IERC20Detailed(Mainnet.CADC); 33 | IERC20Detailed euroc = IERC20Detailed(Mainnet.EUROC); 34 | 35 | IOracle usdcOracle = IOracle(Mainnet.CHAINLINK_USDC_USD); 36 | IOracle cadcOracle = IOracle(Mainnet.CHAINLINK_CAD_USD); 37 | IOracle eurocOracle = IOracle(Mainnet.CHAINLINK_EUR_USD); 38 | 39 | Curve dfxCadcCurve; 40 | Curve dfxEurocCurve; 41 | 42 | int128 public protocolFee = 50000; 43 | 44 | function setUp() public { 45 | treasury = new MockUser(); 46 | newTreasury = new MockUser(); 47 | liquidityProvider = new MockUser(); 48 | 49 | config = new Config(protocolFee,address(treasury)); 50 | 51 | cheats.startPrank(address(treasury)); 52 | assimilatorFactory = new AssimilatorFactory(); 53 | curveFactory = new CurveFactoryV2( 54 | address(assimilatorFactory), 55 | address(config) 56 | ); 57 | 58 | assimilatorFactory.setCurveFactory(address(curveFactory)); 59 | cheats.stopPrank(); 60 | } 61 | 62 | function testUpdatingIncorrectOracles() public { // wrong/malicious info 63 | cheats.startPrank(address(treasury)); 64 | // Incorrect oracles, with correct token pairing and decimals 65 | CurveInfo memory badCadcCurveInfo = CurveInfo( 66 | string.concat("dfx-", cadc.name()), 67 | string.concat("dfx-", cadc.symbol()), 68 | // Haechi #5 69 | // 1. locks USDC as the quote token 70 | // 2. removes token decimals field and just retrieves them from the tokens themselves 71 | // as long as base token remains the same there will be no issues with changing to a new oracle 72 | address(cadc), 73 | address(usdc), 74 | DefaultCurve.BASE_WEIGHT, 75 | DefaultCurve.QUOTE_WEIGHT, 76 | eurocOracle, 77 | eurocOracle, 78 | DefaultCurve.ALPHA, 79 | DefaultCurve.BETA, 80 | DefaultCurve.MAX, 81 | DefaultCurve.EPSILON, 82 | DefaultCurve.LAMBDA 83 | ); 84 | dfxCadcCurve = curveFactory.newCurve(badCadcCurveInfo); 85 | 86 | console.log(address(assimilatorFactory.getAssimilator(address(cadc)))); 87 | 88 | // Assim address from the factory 89 | AssimilatorV2 cadcFactoryAssimBefore = assimilatorFactory.getAssimilator(address(cadc)); 90 | AssimilatorV2 usdcFactoryAssimBefore = assimilatorFactory.getAssimilator(address(usdc)); 91 | 92 | // Assim address from the curve 93 | IAssimilator cadcCurveAssimBefore = IAssimilator(dfxCadcCurve.assimilator(address(cadc))); 94 | IAssimilator usdcCurveAssimBefore = IAssimilator(dfxCadcCurve.assimilator(address(usdc))); 95 | 96 | // Curve vs Factory assim 97 | assertTrue(cadcCurveAssimBefore == cadcFactoryAssimBefore, "before-update/curve and factory cadc assimilators are not the same"); 98 | assertTrue(usdcCurveAssimBefore == usdcFactoryAssimBefore, "before-update/curve and factory usdc assimilators are not the same"); 99 | 100 | uint256 cadcRateBefore = cadcCurveAssimBefore.getRate(); 101 | uint256 usdcRateBefore = usdcCurveAssimBefore.getRate(); 102 | 103 | // Bad front ran oracles rates should be the same for USDC vs CADC 104 | assertTrue(cadcCurveAssimBefore.getRate() == usdcCurveAssimBefore.getRate(), "before-update/usdc and cadc rates are not the same"); 105 | 106 | // PAUSE THE POOLS 107 | dfxCadcCurve.setFrozen(true); 108 | 109 | // Only owner revoking assimilators 110 | assimilatorFactory.revokeAssimilator(address(cadc)); 111 | assimilatorFactory.revokeAssimilator(address(usdc)); 112 | 113 | // Create new assimilators for respective tokens 114 | assimilatorFactory.newAssimilator( 115 | cadcOracle, 116 | address(cadc), 117 | 18 118 | ); 119 | 120 | assimilatorFactory.newAssimilator( 121 | usdcOracle, 122 | address(usdc), 123 | 6 124 | ); 125 | 126 | IAssimilator cadcFactoryAssimNew = assimilatorFactory.getAssimilator(address(cadc)); 127 | IAssimilator usdcFactoryAssimNew = assimilatorFactory.getAssimilator(address(usdc)); 128 | 129 | // New assimilator should be different than the old 130 | assertTrue(cadcFactoryAssimNew != cadcFactoryAssimBefore, "after-update/curve and factory cadc assimilators are the same"); 131 | assertTrue(usdcFactoryAssimNew != usdcFactoryAssimBefore, "after-update/curve and factory usdc assimilators are the same"); 132 | 133 | // Set new assimilators 134 | dfxCadcCurve.setAssimilator( 135 | address(cadc), 136 | address(cadcFactoryAssimNew), 137 | address(usdc), 138 | address(usdcFactoryAssimNew) 139 | ); 140 | 141 | IAssimilator cadcCurveAssimNew = IAssimilator(dfxCadcCurve.assimilator(address(cadc))); 142 | IAssimilator usdcCurveAssimNew = IAssimilator(dfxCadcCurve.assimilator(address(usdc))); 143 | 144 | // Curve vs Factory assim 145 | assertTrue(cadcFactoryAssimNew == cadcCurveAssimNew, "after-update/curve and factory cadc assimilators are not the same"); 146 | assertTrue(usdcFactoryAssimNew == usdcCurveAssimNew, "after-update/curve and factory usdc assimilators are not the same"); 147 | 148 | // Rates should be all different now and non zero 149 | uint256 cadcRateAfter = cadcCurveAssimNew.getRate(); 150 | uint256 usdcRateAfter = usdcCurveAssimNew.getRate(); 151 | 152 | assertTrue(cadcRateAfter != usdcRateAfter, "after-update/cadc and usdc rates are the same"); 153 | console.log(cadcRateAfter, usdcRateAfter); 154 | assertTrue(cadcRateAfter != 0, "after-update/zero cadc rate"); 155 | assertTrue(usdcRateAfter != 0, "after-update/zero usdc rate"); 156 | 157 | // Comparing to old rates 158 | assertTrue(cadcRateAfter != cadcRateBefore, "after-update/cadc rates are the same as before"); 159 | assertTrue(usdcRateAfter != usdcRateBefore, "after-update/usdc rates are the same as before"); 160 | 161 | // UNPAUSE THE POOLS 162 | dfxCadcCurve.setFrozen(false); 163 | console.log(address(assimilatorFactory.getAssimilator(address(cadc)))); 164 | console.log(address(assimilatorFactory.getAssimilator(address(usdc)))); 165 | 166 | 167 | // console.log(dfxCadcCurve.assimilator(address(cadc))); 168 | // console.log(dfxCadcCurve.assimilator(address(usdc))); 169 | cheats.stopPrank(); 170 | 171 | // Check swaps, withdrawals, and deposits 172 | cheats.startPrank(address(liquidityProvider)); 173 | deal(address(cadc), address(liquidityProvider), 100_000e18); 174 | deal(address(usdc), address(liquidityProvider), 100_000e6); 175 | cadc.approve(address(dfxCadcCurve), type(uint).max); 176 | usdc.approve(address(dfxCadcCurve), type(uint).max); 177 | dfxCadcCurve.deposit(100_000e18,0,0,type(uint256).max, type(uint256).max, block.timestamp + 60); 178 | 179 | uint256 cadcBalanceBeforeSwap = cadc.balanceOf(address(liquidityProvider)); 180 | uint256 usdcBalanceBeforeSwap = usdc.balanceOf(address(liquidityProvider)); 181 | console.log(cadcBalanceBeforeSwap); 182 | console.log(usdcBalanceBeforeSwap); 183 | 184 | dfxCadcCurve.originSwap(address(usdc), address(cadc), 10_000e6, 0, block.timestamp + 60); 185 | dfxCadcCurve.originSwap(address(cadc), address(usdc), uint256(10_000e18).div(cadcRateAfter).mul(1e8), 0, block.timestamp + 60); 186 | 187 | uint256 cadcBalanceAfterSwap = cadc.balanceOf(address(liquidityProvider)); 188 | uint256 usdcBalanceAfterSwap = usdc.balanceOf(address(liquidityProvider)); 189 | 190 | assertApproxEqRel(cadcBalanceAfterSwap, cadcBalanceBeforeSwap, 0.01e18); 191 | assertApproxEqRel(usdcBalanceAfterSwap, usdcBalanceBeforeSwap, 0.01e18); 192 | 193 | dfxCadcCurve.withdraw(dfxCadcCurve.balanceOf(address(liquidityProvider)), block.timestamp + 60); 194 | uint256 cadcBalanceAfterWithdraw = cadc.balanceOf(address(liquidityProvider)); 195 | uint256 usdcBalanceAfterWithdraw = usdc.balanceOf(address(liquidityProvider)); 196 | 197 | assertApproxEqRel(cadcBalanceAfterWithdraw, 100_000e18, 0.01e18); 198 | assertApproxEqRel(usdcBalanceAfterWithdraw, 100_000e6, 0.01e18); 199 | 200 | cheats.stopPrank(); 201 | } 202 | 203 | // Make sure random people cant revoke assimilators 204 | function testFail_notOwnerRevokeAssimilator() public { 205 | CurveInfo memory goodCadcCurveInfo = CurveInfo( 206 | string.concat("dfx-", cadc.name()), 207 | string.concat("dfx-", cadc.symbol()), 208 | address(cadc), 209 | address(usdc), 210 | DefaultCurve.BASE_WEIGHT, 211 | DefaultCurve.QUOTE_WEIGHT, 212 | cadcOracle, 213 | usdcOracle, 214 | DefaultCurve.ALPHA, 215 | DefaultCurve.BETA, 216 | DefaultCurve.MAX, 217 | DefaultCurve.EPSILON, 218 | DefaultCurve.LAMBDA 219 | ); 220 | dfxCadcCurve = curveFactory.newCurve(goodCadcCurveInfo); 221 | 222 | // Someone trying to mess up an exisiting assimilators 223 | assimilatorFactory.revokeAssimilator(address(cadc)); 224 | assimilatorFactory.revokeAssimilator(address(usdc)); 225 | } 226 | 227 | function testFail_NotOwnerNewAssimilator() public { 228 | CurveInfo memory goodCadcCurveInfo = CurveInfo( 229 | string.concat("dfx-", cadc.name()), 230 | string.concat("dfx-", cadc.symbol()), 231 | address(cadc), 232 | address(usdc), 233 | DefaultCurve.BASE_WEIGHT, 234 | DefaultCurve.QUOTE_WEIGHT, 235 | cadcOracle, 236 | usdcOracle, 237 | DefaultCurve.ALPHA, 238 | DefaultCurve.BETA, 239 | DefaultCurve.MAX, 240 | DefaultCurve.EPSILON, 241 | DefaultCurve.LAMBDA 242 | ); 243 | dfxCadcCurve = curveFactory.newCurve(goodCadcCurveInfo); 244 | 245 | IAssimilator usdcFactoryAssimNew = assimilatorFactory.getAssimilator(address(usdc)); 246 | 247 | // Someone trying to mess up an exisiting curve 248 | dfxCadcCurve.setAssimilator( 249 | address(usdc), 250 | address(usdcFactoryAssimNew), 251 | address(usdc), 252 | address(usdcFactoryAssimNew) 253 | ); 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /test/Haechi/12.sol: -------------------------------------------------------------------------------- 1 | 2 | // SPDX-License-Identifier: UNLICENSED 3 | pragma solidity ^0.8.10; 4 | 5 | import "forge-std/Test.sol"; 6 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 7 | import "@openzeppelin/contracts/utils/math/SafeMath.sol"; 8 | 9 | import "../../src/interfaces/IAssimilator.sol"; 10 | import "../../src/interfaces/IOracle.sol"; 11 | import "../../src/interfaces/IERC20Detailed.sol"; 12 | import "../../src/AssimilatorFactory.sol"; 13 | import "../../src/CurveFactoryV2.sol"; 14 | import "../../src/Curve.sol"; 15 | import "../../src/Config.sol"; 16 | import "../../src/Structs.sol"; 17 | import "../../src/lib/ABDKMath64x64.sol"; 18 | 19 | import ".././lib/MockUser.sol"; 20 | import ".././lib/CheatCodes.sol"; 21 | import ".././lib/Address.sol"; 22 | import ".././lib/CurveParams.sol"; 23 | import ".././lib/MockChainlinkOracle.sol"; 24 | import ".././lib/MockOracleFactory.sol"; 25 | import ".././lib/MockToken.sol"; 26 | 27 | import ".././utils/Utils.sol"; 28 | 29 | contract SwapFeeTest is Test { 30 | using SafeMath for uint256; 31 | CheatCodes cheats = CheatCodes(HEVM_ADDRESS); 32 | Utils utils; 33 | 34 | // account order is lp provider, trader, treasury 35 | MockUser[] public accounts; 36 | 37 | MockOracleFactory oracleFactory; 38 | // token order is gold, euroc, cadc, usdc 39 | IERC20Detailed[] public tokens; 40 | IOracle[] public oracles; 41 | Curve[] public curves; 42 | uint256[] public decimals; 43 | 44 | uint256[] public dividends = [20, 2]; 45 | 46 | CurveFactoryV2 curveFactory; 47 | AssimilatorFactory assimFactory; 48 | Config config; 49 | 50 | int128 public protocolFee = 50000; 51 | 52 | function setUp() public { 53 | utils = new Utils(); 54 | // create temp accounts 55 | for (uint256 i = 0; i < 4; ++i) { 56 | accounts.push(new MockUser()); 57 | } 58 | // deploy gold token & init 3 stable coins 59 | MockToken gold = new MockToken(); 60 | tokens.push(IERC20Detailed(address(gold))); 61 | tokens.push(IERC20Detailed(Mainnet.EUROC)); 62 | tokens.push(IERC20Detailed(Mainnet.CADC)); 63 | tokens.push(IERC20Detailed(Mainnet.USDC)); 64 | 65 | // deploy mock oracle factory for deployed token (named gold) 66 | oracleFactory = new MockOracleFactory(); 67 | oracles.push( 68 | oracleFactory.newOracle( 69 | address(tokens[0]), 70 | "goldOracle", 71 | 9, 72 | 20000000000 73 | ) 74 | ); 75 | oracles.push(IOracle(Mainnet.CHAINLINK_EUR_USD)); 76 | oracles.push(IOracle(Mainnet.CHAINLINK_CAD_USD)); 77 | oracles.push(IOracle(Mainnet.CHAINLINK_USDC_USD)); 78 | 79 | config = new Config(protocolFee,address(accounts[2])); 80 | // deploy new assimilator factory & curveFactory v2 81 | 82 | assimFactory = new AssimilatorFactory(); 83 | curveFactory = new CurveFactoryV2( 84 | address(assimFactory), 85 | address(config) 86 | ); 87 | assimFactory.setCurveFactory(address(curveFactory)); 88 | // now deploy curves 89 | cheats.startPrank(address(accounts[2])); 90 | for (uint256 i = 0; i < 3; ++i) { 91 | CurveInfo memory curveInfo = CurveInfo( 92 | string(abi.encode("dfx-curve-", i)), 93 | string(abi.encode("lp-", i)), 94 | address(tokens[i]), 95 | address(tokens[3]), 96 | DefaultCurve.BASE_WEIGHT, 97 | DefaultCurve.QUOTE_WEIGHT, 98 | oracles[i], 99 | oracles[3], 100 | DefaultCurve.ALPHA, 101 | DefaultCurve.BETA, 102 | DefaultCurve.MAX, 103 | DefaultCurve.EPSILON, 104 | DefaultCurve.LAMBDA 105 | ); 106 | Curve _curve = curveFactory.newCurve(curveInfo); 107 | curves.push(_curve); 108 | } 109 | cheats.stopPrank(); 110 | 111 | // now mint gold & silver tokens 112 | uint256 mintAmt = 300_000_000_000; 113 | for (uint256 i = 0; i < 4; ++i) { 114 | decimals.push(utils.tenToPowerOf(tokens[i].decimals())); 115 | if (i == 0) { 116 | tokens[0].mint(address(accounts[0]), mintAmt.mul(decimals[i])); 117 | } else { 118 | deal( 119 | address(tokens[i]), 120 | address(accounts[0]), 121 | mintAmt.mul(decimals[i]) 122 | ); 123 | } 124 | } 125 | // now approve 126 | cheats.startPrank(address(accounts[0])); 127 | for (uint256 i = 0; i < 3; ++i) { 128 | tokens[i].approve(address(curves[i]), type(uint256).max); 129 | tokens[3].approve(address(curves[i]), type(uint256).max); 130 | } 131 | cheats.stopPrank(); 132 | } 133 | 134 | // // test swap of forex stable coin(euroc, cadc) usdc 135 | function testSwapFromForexToUsdcFee(uint256 amt) public { 136 | cheats.assume(amt > 100); 137 | cheats.assume(amt < 10000000); 138 | // uint256 amt = 1000000; 139 | for (uint256 i = 0; i < 2; ++i) { 140 | // mint token to trader 141 | deal( 142 | address(tokens[i + 1]), 143 | address(accounts[1]), 144 | amt * decimals[i + 1] 145 | ); 146 | 147 | uint256 forexBalance = tokens[i + 1].balanceOf( 148 | address(accounts[1]) 149 | ); 150 | 151 | cheats.startPrank(address(accounts[1])); 152 | tokens[i + 1].approve(address(curves[i + 1]), type(uint256).max); 153 | tokens[3].approve(address(curves[i + 1]), type(uint256).max); 154 | cheats.stopPrank(); 155 | 156 | // first deposit 157 | cheats.startPrank(address(accounts[0])); 158 | curves[i + 1].deposit(1000000000 * 1e18,0,0,type(uint256).max, type(uint256).max, block.timestamp + 60); 159 | cheats.stopPrank(); 160 | 161 | cheats.startPrank(address(accounts[1])); 162 | // now swap forex stable coin to usdc 163 | curves[i + 1].originSwap( 164 | address(tokens[i + 1]), 165 | address(tokens[3]), 166 | forexBalance, 167 | 0, 168 | block.timestamp + 60 169 | ); 170 | cheats.stopPrank(); 171 | console.log(tokens[3].balanceOf(address(accounts[1]))); 172 | console.log(tokens[3].balanceOf(address(accounts[2]))); 173 | assertApproxEqAbs( 174 | (tokens[3].balanceOf(address(accounts[1])) * 20) / 100000, 175 | tokens[3].balanceOf(address(accounts[2])), 176 | tokens[3].balanceOf(address(accounts[1])) / 100000 177 | ); 178 | } 179 | } 180 | 181 | // // test swap of forex stable coin(euroc, cadc) usdc 182 | function testSwapFromUsdcToForexFee(uint256 amt) public { 183 | cheats.assume(amt > 100); 184 | cheats.assume(amt < 10000000); 185 | // uint256 amt = 1000000; 186 | for (uint256 i = 0; i < 2; ++i) { 187 | // mint token to trader 188 | deal(address(tokens[3]), address(accounts[1]), amt * decimals[3]); 189 | 190 | uint256 usdcBalance = tokens[3].balanceOf(address(accounts[1])); 191 | 192 | cheats.startPrank(address(accounts[1])); 193 | tokens[i + 1].approve(address(curves[i + 1]), type(uint256).max); 194 | tokens[3].approve(address(curves[i + 1]), type(uint256).max); 195 | cheats.stopPrank(); 196 | 197 | // first deposit 198 | cheats.startPrank(address(accounts[0])); 199 | curves[i + 1].deposit(1000000000 * 1e18,0,0,type(uint256).max, type(uint256).max, block.timestamp + 60); 200 | cheats.stopPrank(); 201 | 202 | cheats.startPrank(address(accounts[1])); 203 | // now swap usdc to forex stable coin 204 | curves[i + 1].originSwap( 205 | address(tokens[3]), 206 | address(tokens[i + 1]), 207 | usdcBalance, 208 | 0, 209 | block.timestamp + 60 210 | ); 211 | cheats.stopPrank(); 212 | assertApproxEqAbs( 213 | (tokens[i + 1].balanceOf(address(accounts[1])) * 20) / 100000, 214 | tokens[i + 1].balanceOf(address(accounts[2])), 215 | tokens[i + 1].balanceOf(address(accounts[1])) / 100000 216 | ); 217 | } 218 | } 219 | } -------------------------------------------------------------------------------- /test/Haechi/13.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.10; 3 | 4 | import "forge-std/Test.sol"; 5 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 6 | import "@openzeppelin/contracts/utils/math/SafeMath.sol"; 7 | 8 | import "../../src/interfaces/IAssimilator.sol"; 9 | import "../../src/interfaces/IOracle.sol"; 10 | import "../../src/interfaces/IERC20Detailed.sol"; 11 | import "../../src/AssimilatorFactory.sol"; 12 | import "../../src/CurveFactoryV2.sol"; 13 | import "../../src/Curve.sol"; 14 | import "../../src/Config.sol"; 15 | import "../../src/Structs.sol"; 16 | import "../../src/lib/ABDKMath64x64.sol"; 17 | 18 | import ".././lib/MockUser.sol"; 19 | import ".././lib/CheatCodes.sol"; 20 | import ".././lib/Address.sol"; 21 | import ".././lib/CurveParams.sol"; 22 | import ".././lib/MockChainlinkOracle.sol"; 23 | import ".././lib/MockOracleFactory.sol"; 24 | import ".././lib/MockToken.sol"; 25 | 26 | import ".././utils/Utils.sol"; 27 | 28 | contract MinDepositTest is Test { 29 | using SafeMath for uint256; 30 | CheatCodes cheats = CheatCodes(HEVM_ADDRESS); 31 | Utils utils; 32 | 33 | // account order is lp provider, trader, treasury 34 | MockUser[] public accounts; 35 | 36 | MockOracleFactory oracleFactory; 37 | // token order is gold, euroc, cadc, usdc 38 | IERC20Detailed[] public tokens; 39 | IOracle[] public oracles; 40 | Curve[] public curves; 41 | uint256[] public decimals; 42 | 43 | CurveFactoryV2 curveFactory; 44 | AssimilatorFactory assimFactory; 45 | Config config; 46 | 47 | int128 public protocolFee = 50000; 48 | 49 | function setUp() public { 50 | utils = new Utils(); 51 | // create temp accounts 52 | for (uint256 i = 0; i < 4; ++i) { 53 | accounts.push(new MockUser()); 54 | } 55 | // deploy gold token & init 3 stable coins 56 | MockToken gold = new MockToken(); 57 | tokens.push(IERC20Detailed(address(gold))); 58 | tokens.push(IERC20Detailed(Mainnet.EUROC)); 59 | tokens.push(IERC20Detailed(Mainnet.CADC)); 60 | tokens.push(IERC20Detailed(Mainnet.USDC)); 61 | 62 | // deploy mock oracle factory for deployed token (named gold) 63 | oracleFactory = new MockOracleFactory(); 64 | oracles.push( 65 | oracleFactory.newOracle( 66 | address(tokens[0]), 67 | "goldOracle", 68 | 9, 69 | 20000000000 70 | ) 71 | ); 72 | oracles.push(IOracle(Mainnet.CHAINLINK_EUR_USD)); 73 | oracles.push(IOracle(Mainnet.CHAINLINK_CAD_USD)); 74 | oracles.push(IOracle(Mainnet.CHAINLINK_USDC_USD)); 75 | 76 | config = new Config(protocolFee,address(accounts[2])); 77 | 78 | // deploy new assimilator factory & curveFactory v2 79 | assimFactory = new AssimilatorFactory(); 80 | curveFactory = new CurveFactoryV2( 81 | address(assimFactory), 82 | address(config) 83 | ); 84 | assimFactory.setCurveFactory(address(curveFactory)); 85 | // now deploy curves 86 | cheats.startPrank(address(accounts[2])); 87 | for (uint256 i = 0; i < 3; ++i) { 88 | CurveInfo memory curveInfo = CurveInfo( 89 | string(abi.encode("dfx-curve-", i)), 90 | string(abi.encode("lp-", i)), 91 | address(tokens[i]), 92 | address(tokens[3]), 93 | DefaultCurve.BASE_WEIGHT, 94 | DefaultCurve.QUOTE_WEIGHT, 95 | oracles[i], 96 | oracles[3], 97 | DefaultCurve.ALPHA, 98 | DefaultCurve.BETA, 99 | DefaultCurve.MAX, 100 | DefaultCurve.EPSILON, 101 | DefaultCurve.LAMBDA 102 | ); 103 | Curve _curve = curveFactory.newCurve(curveInfo); 104 | curves.push(_curve); 105 | } 106 | cheats.stopPrank(); 107 | 108 | // now mint gold & silver tokens 109 | uint256 mintAmt = 300_000_000_000; 110 | for (uint256 i = 0; i < 4; ++i) { 111 | decimals.push(utils.tenToPowerOf(tokens[i].decimals())); 112 | if (i == 0) { 113 | tokens[0].mint(address(accounts[0]), mintAmt.mul(decimals[i])); 114 | } else { 115 | deal( 116 | address(tokens[i]), 117 | address(accounts[0]), 118 | mintAmt.mul(decimals[i]) 119 | ); 120 | } 121 | } 122 | // now approve 123 | cheats.startPrank(address(accounts[0])); 124 | for (uint256 i = 0; i < 3; ++i) { 125 | tokens[i].approve(address(curves[i]), type(uint256).max); 126 | tokens[3].approve(address(curves[i]), type(uint256).max); 127 | } 128 | cheats.stopPrank(); 129 | } 130 | 131 | function testMinDeposit() public { 132 | uint256 amt = 1000000; 133 | // mint token to trader 134 | deal(address(tokens[1]), address(accounts[1]), amt * decimals[1]); 135 | 136 | uint256 eurocToSend = 1000000; 137 | // mint tokens to attacker 138 | deal(address(tokens[1]), address(curves[1]), eurocToSend); 139 | deal(address(tokens[3]), address(curves[1]), eurocToSend * 100); 140 | 141 | uint256 forexBalance = tokens[1].balanceOf(address(accounts[1])); 142 | 143 | cheats.startPrank(address(accounts[1])); 144 | tokens[1].approve(address(curves[1]), type(uint256).max); 145 | tokens[3].approve(address(curves[1]), type(uint256).max); 146 | cheats.stopPrank(); 147 | // destroy balance by first sending 100:1 Usdc : Euroc ratio 148 | 149 | uint256 minEurocToDeposit = 500000000000000; 150 | uint256 minUsdcToDeposit = 350000000000000; 151 | // first deposit 152 | cheats.startPrank(address(accounts[0])); 153 | curves[1].deposit(1000000000 * 1e18, 0, 0,type(uint256).max,type(uint256).max, block.timestamp + 60); 154 | cheats.stopPrank(); 155 | 156 | // read bal 157 | uint256 crvEurocBal = tokens[1].balanceOf(address(curves[1])); 158 | uint256 crvUsdcBal = tokens[3].balanceOf(address(curves[1])); 159 | console.logString("original balance"); 160 | console.log(eurocToSend); 161 | console.log(eurocToSend * 100); 162 | console.logString("new balance"); 163 | console.log(crvEurocBal); 164 | console.log(crvUsdcBal); 165 | } 166 | 167 | function testFailMinDeposit() public { 168 | uint256 amt = 1000000; 169 | // mint token to trader 170 | deal(address(tokens[1]), address(accounts[1]), amt * decimals[1]); 171 | 172 | uint256 eurocToSend = 1000000; 173 | // destroy balance by first sending 100:1 Usdc : Euroc ratio 174 | deal(address(tokens[1]), address(curves[1]), eurocToSend); 175 | deal(address(tokens[3]), address(curves[1]), eurocToSend * 100); 176 | 177 | uint256 forexBalance = tokens[1].balanceOf(address(accounts[1])); 178 | 179 | cheats.startPrank(address(accounts[1])); 180 | tokens[1].approve(address(curves[1]), type(uint256).max); 181 | tokens[3].approve(address(curves[1]), type(uint256).max); 182 | cheats.stopPrank(); 183 | 184 | uint256 minEurocToDeposit = 500000000000000; 185 | uint256 minUsdcToDeposit = 350000000000000; 186 | // deposit 187 | cheats.startPrank(address(accounts[0])); 188 | curves[1].deposit( 189 | 1000000000 * 1e18, 190 | minUsdcToDeposit, 191 | minEurocToDeposit, 192 | 0, 193 | 0, 194 | block.timestamp + 60 195 | ); 196 | cheats.stopPrank(); 197 | } 198 | 199 | function testFailMaxDeposit() public { 200 | uint256 amt = 10000000; 201 | // mint token to trader 202 | deal(address(tokens[1]), address(accounts[1]), amt * decimals[1]); 203 | 204 | // mint 100 euro, 1 usdc to the pool 205 | deal(address(tokens[1]), address(curves[1]), 100 * decimals[1]); 206 | deal(address(tokens[3]), address(curves[1]), 1 * decimals[3]); 207 | 208 | uint256 forexBalance = tokens[1].balanceOf(address(accounts[1])); 209 | 210 | cheats.startPrank(address(accounts[1])); 211 | tokens[1].approve(address(curves[1]), type(uint256).max); 212 | tokens[3].approve(address(curves[1]), type(uint256).max); 213 | cheats.stopPrank(); 214 | 215 | uint256 maxEurocToDeposit = 600000*decimals[1]; 216 | uint256 maxUsdcToDeposit = 700000 * decimals[3]; 217 | // deposit 218 | cheats.startPrank(address(accounts[0])); 219 | curves[1].deposit( 220 | 1000000 * 1e18, 221 | 0, 222 | 0, 223 | maxUsdcToDeposit, 224 | maxEurocToDeposit, 225 | block.timestamp + 60 226 | ); 227 | cheats.stopPrank(); 228 | } 229 | } -------------------------------------------------------------------------------- /test/Haechi/14.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.10; 3 | 4 | import "forge-std/Test.sol"; 5 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 6 | import "@openzeppelin/contracts/utils/math/SafeMath.sol"; 7 | 8 | import "../../src/interfaces/IAssimilator.sol"; 9 | import "../../src/interfaces/IOracle.sol"; 10 | import "../../src/interfaces/IERC20Detailed.sol"; 11 | import "../../src/AssimilatorFactory.sol"; 12 | import "../../src/CurveFactoryV2.sol"; 13 | import "../../src/Curve.sol"; 14 | import "../../src/Config.sol"; 15 | import "../../src/Structs.sol"; 16 | import "../../src/lib/ABDKMath64x64.sol"; 17 | 18 | import ".././lib/MockUser.sol"; 19 | import ".././lib/CheatCodes.sol"; 20 | import ".././lib/Address.sol"; 21 | import ".././lib/CurveParams.sol"; 22 | import ".././lib/MockChainlinkOracle.sol"; 23 | import ".././lib/MockOracleFactory.sol"; 24 | import ".././lib/MockToken.sol"; 25 | 26 | import ".././utils/Utils.sol"; 27 | 28 | contract MinimalLiquidityLockTest is Test { 29 | using SafeMath for uint256; 30 | CheatCodes cheats = CheatCodes(HEVM_ADDRESS); 31 | Utils utils; 32 | 33 | // account order is lp provider, trader, treasury 34 | MockUser[] public accounts; 35 | 36 | MockOracleFactory oracleFactory; 37 | // token order is gold, euroc, cadc, usdc 38 | IERC20Detailed[] public tokens; 39 | IOracle[] public oracles; 40 | Curve[] public curves; 41 | uint256[] public decimals; 42 | 43 | CurveFactoryV2 curveFactory; 44 | AssimilatorFactory assimFactory; 45 | Config config; 46 | 47 | int128 public protocolFee = 50000; 48 | 49 | function setUp() public { 50 | utils = new Utils(); 51 | // create temp accounts 52 | for (uint256 i = 0; i < 4; ++i) { 53 | accounts.push(new MockUser()); 54 | } 55 | // deploy gold token & init 3 stable coins 56 | MockToken gold = new MockToken(); 57 | tokens.push(IERC20Detailed(address(gold))); 58 | tokens.push(IERC20Detailed(Mainnet.EUROC)); 59 | tokens.push(IERC20Detailed(Mainnet.CADC)); 60 | tokens.push(IERC20Detailed(Mainnet.USDC)); 61 | 62 | // deploy mock oracle factory for deployed token (named gold) 63 | oracleFactory = new MockOracleFactory(); 64 | oracles.push( 65 | oracleFactory.newOracle( 66 | address(tokens[0]), 67 | "goldOracle", 68 | 9, 69 | 20000000000 70 | ) 71 | ); 72 | oracles.push(IOracle(Mainnet.CHAINLINK_EUR_USD)); 73 | oracles.push(IOracle(Mainnet.CHAINLINK_CAD_USD)); 74 | oracles.push(IOracle(Mainnet.CHAINLINK_USDC_USD)); 75 | 76 | config = new Config(protocolFee,address(accounts[2])); 77 | 78 | // deploy new assimilator factory & curveFactory v2 79 | assimFactory = new AssimilatorFactory(); 80 | curveFactory = new CurveFactoryV2( 81 | address(assimFactory), 82 | address(config) 83 | ); 84 | assimFactory.setCurveFactory(address(curveFactory)); 85 | // now deploy curves 86 | cheats.startPrank(address(accounts[2])); 87 | for (uint256 i = 0; i < 3; ++i) { 88 | CurveInfo memory curveInfo = CurveInfo( 89 | string(abi.encode("dfx-curve-", i)), 90 | string(abi.encode("lp-", i)), 91 | address(tokens[i]), 92 | address(tokens[3]), 93 | DefaultCurve.BASE_WEIGHT, 94 | DefaultCurve.QUOTE_WEIGHT, 95 | oracles[i], 96 | oracles[3], 97 | DefaultCurve.ALPHA, 98 | DefaultCurve.BETA, 99 | DefaultCurve.MAX, 100 | DefaultCurve.EPSILON, 101 | DefaultCurve.LAMBDA 102 | ); 103 | Curve _curve = curveFactory.newCurve(curveInfo); 104 | curves.push(_curve); 105 | } 106 | cheats.stopPrank(); 107 | 108 | // now mint gold & silver tokens 109 | uint256 mintAmt = 300_000_000_000; 110 | for (uint256 i = 0; i < 4; ++i) { 111 | decimals.push(utils.tenToPowerOf(tokens[i].decimals())); 112 | if (i == 0) { 113 | tokens[0].mint(address(accounts[0]), mintAmt.mul(decimals[i])); 114 | } else { 115 | deal( 116 | address(tokens[i]), 117 | address(accounts[0]), 118 | mintAmt.mul(decimals[i]) 119 | ); 120 | } 121 | } 122 | // now approve 123 | cheats.startPrank(address(accounts[0])); 124 | for (uint256 i = 0; i < 3; ++i) { 125 | tokens[i].approve(address(curves[i]), type(uint256).max); 126 | tokens[3].approve(address(curves[i]), type(uint256).max); 127 | } 128 | cheats.stopPrank(); 129 | } 130 | 131 | function testFirstDeposit() public { 132 | uint256 amt = 100000000000; 133 | // mint token to trader 134 | deal(address(tokens[1]), address(accounts[1]), amt * decimals[1]); 135 | deal(address(tokens[3]), address(accounts[1]), amt * decimals[3]); 136 | 137 | cheats.startPrank(address(accounts[1])); 138 | tokens[1].approve(address(curves[1]), type(uint256).max); 139 | tokens[3].approve(address(curves[1]), type(uint256).max); 140 | cheats.stopPrank(); 141 | 142 | // first deposit 143 | cheats.startPrank(address(accounts[1])); 144 | curves[1].deposit(1000000000 * 1e18, 0, 0,type(uint256).max,type(uint256).max, block.timestamp + 60); 145 | // second deposit 146 | curves[1].deposit(1 * 1e18, 0, 0,type(uint256).max,type(uint256).max, block.timestamp + 60); 147 | cheats.stopPrank(); 148 | uint256 locked = curves[1].balanceOf(address(0)); 149 | assert(locked == 1e6); 150 | } 151 | } -------------------------------------------------------------------------------- /test/Haechi/6.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.10; 3 | 4 | import "forge-std/Test.sol"; 5 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 6 | import "@openzeppelin/contracts/utils/math/SafeMath.sol"; 7 | 8 | import "../../src/interfaces/IAssimilator.sol"; 9 | import "../../src/interfaces/IOracle.sol"; 10 | import "../../src/interfaces/IERC20Detailed.sol"; 11 | import "../../src/AssimilatorFactory.sol"; 12 | import "../../src/CurveFactoryV2.sol"; 13 | import "../../src/Curve.sol"; 14 | import "../../src/Config.sol"; 15 | import "../../src/Structs.sol"; 16 | import "../../src/lib/ABDKMath64x64.sol"; 17 | 18 | import ".././lib/MockUser.sol"; 19 | import ".././lib/CheatCodes.sol"; 20 | import ".././lib/Address.sol"; 21 | import ".././lib/CurveParams.sol"; 22 | import ".././lib/MockChainlinkOracle.sol"; 23 | import ".././lib/MockOracleFactory.sol"; 24 | import ".././lib/MockToken.sol"; 25 | 26 | import ".././utils/Utils.sol"; 27 | 28 | contract TargetSwapFeeTest is Test { 29 | using SafeMath for uint256; 30 | CheatCodes cheats = CheatCodes(HEVM_ADDRESS); 31 | Utils utils; 32 | 33 | // account order is lp provider, trader, treasury 34 | MockUser[] public accounts; 35 | 36 | MockOracleFactory oracleFactory; 37 | // token order is gold, euroc, cadc, usdc 38 | IERC20Detailed[] public tokens; 39 | IOracle[] public oracles; 40 | Curve[] public curves; 41 | uint256[] public decimals; 42 | 43 | uint256[] public dividends = [20, 2]; 44 | 45 | CurveFactoryV2 curveFactory; 46 | AssimilatorFactory assimFactory; 47 | Config config; 48 | 49 | int128 public protocolFee = 50000; 50 | 51 | function setUp() public { 52 | utils = new Utils(); 53 | // create temp accounts 54 | for (uint256 i = 0; i < 4; ++i) { 55 | accounts.push(new MockUser()); 56 | } 57 | // deploy gold token & init 3 stable coins 58 | MockToken gold = new MockToken(); 59 | tokens.push(IERC20Detailed(address(gold))); 60 | tokens.push(IERC20Detailed(Mainnet.EUROC)); 61 | tokens.push(IERC20Detailed(Mainnet.CADC)); 62 | tokens.push(IERC20Detailed(Mainnet.USDC)); 63 | 64 | // deploy mock oracle factory for deployed token (named gold) 65 | oracleFactory = new MockOracleFactory(); 66 | oracles.push( 67 | oracleFactory.newOracle( 68 | address(tokens[0]), 69 | "goldOracle", 70 | 9, 71 | 20000000000 72 | ) 73 | ); 74 | oracles.push(IOracle(Mainnet.CHAINLINK_EUR_USD)); 75 | oracles.push(IOracle(Mainnet.CHAINLINK_CAD_USD)); 76 | oracles.push(IOracle(Mainnet.CHAINLINK_USDC_USD)); 77 | 78 | config = new Config(protocolFee, address(accounts[2])); 79 | 80 | // deploy new assimilator factory & curveFactory v2 81 | assimFactory = new AssimilatorFactory(); 82 | curveFactory = new CurveFactoryV2( 83 | address(assimFactory), 84 | address(config) 85 | ); 86 | assimFactory.setCurveFactory(address(curveFactory)); 87 | // now deploy curves 88 | cheats.startPrank(address(accounts[2])); 89 | for (uint256 i = 0; i < 3; ++i) { 90 | CurveInfo memory curveInfo = CurveInfo( 91 | string(abi.encode("dfx-curve-", i)), 92 | string(abi.encode("lp-", i)), 93 | address(tokens[i]), 94 | address(tokens[3]), 95 | DefaultCurve.BASE_WEIGHT, 96 | DefaultCurve.QUOTE_WEIGHT, 97 | oracles[i], 98 | oracles[3], 99 | DefaultCurve.ALPHA, 100 | DefaultCurve.BETA, 101 | DefaultCurve.MAX, 102 | DefaultCurve.EPSILON, 103 | DefaultCurve.LAMBDA 104 | ); 105 | Curve _curve = curveFactory.newCurve(curveInfo); 106 | curves.push(_curve); 107 | } 108 | cheats.stopPrank(); 109 | 110 | // now mint gold & silver tokens 111 | uint256 mintAmt = 300_000_000_000; 112 | for (uint256 i = 0; i < 4; ++i) { 113 | decimals.push(utils.tenToPowerOf(tokens[i].decimals())); 114 | if (i == 0) { 115 | tokens[0].mint(address(accounts[0]), mintAmt.mul(decimals[i])); 116 | } else { 117 | deal( 118 | address(tokens[i]), 119 | address(accounts[0]), 120 | mintAmt.mul(decimals[i]) 121 | ); 122 | } 123 | } 124 | // now approve 125 | cheats.startPrank(address(accounts[0])); 126 | for (uint256 i = 0; i < 3; ++i) { 127 | tokens[i].approve(address(curves[i]), type(uint256).max); 128 | tokens[3].approve(address(curves[i]), type(uint256).max); 129 | } 130 | cheats.stopPrank(); 131 | } 132 | 133 | function testSwapFromEurocToUsdcFee(uint256 amt) public { 134 | cheats.assume(amt > 100); 135 | cheats.assume(amt < 10000000); 136 | // mint token to trader 137 | deal( 138 | address(tokens[1]), 139 | address(accounts[1]), 140 | amt * decimals[1] * 100 141 | ); 142 | 143 | uint256 eurocBalance = tokens[1].balanceOf( 144 | address(accounts[1]) 145 | ); 146 | 147 | cheats.startPrank(address(accounts[1])); 148 | tokens[1].approve(address(curves[1]), type(uint256).max); 149 | tokens[3].approve(address(curves[1]), type(uint256).max); 150 | cheats.stopPrank(); 151 | 152 | // first deposit 153 | cheats.startPrank(address(accounts[0])); 154 | curves[1].deposit(1000000000 * 1e18,0,0,type(uint256).max, type(uint256).max, block.timestamp + 60); 155 | cheats.stopPrank(); 156 | 157 | uint256 forexBeforeSwap = tokens[1].balanceOf(address(accounts[1])); 158 | 159 | cheats.startPrank(address(accounts[1])); 160 | // now swap forex stable coin to usdc 161 | curves[1].targetSwap( 162 | address(tokens[1]), 163 | address(tokens[3]), 164 | type(uint256).max, 165 | amt * decimals[3], 166 | block.timestamp + 60 167 | ); 168 | cheats.stopPrank(); 169 | uint256 forexAfterSwap = tokens[1].balanceOf(address(accounts[1])); 170 | uint256 diff = forexBeforeSwap - forexAfterSwap; 171 | assertApproxEqAbs( 172 | (diff * 20) / 100000, 173 | tokens[1].balanceOf(address(accounts[2])), 174 | diff / 100000 175 | ); 176 | } 177 | } -------------------------------------------------------------------------------- /test/Haechi/8.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import "forge-std/Test.sol"; 5 | 6 | import "../../src/AssimilatorFactory.sol"; 7 | import "../../src/CurveFactoryV2.sol"; 8 | import "../../src/Curve.sol"; 9 | import "../../src/interfaces/IERC20Detailed.sol"; 10 | 11 | import ".././lib/MockUser.sol"; 12 | import ".././lib/CheatCodes.sol"; 13 | import ".././lib/Address.sol"; 14 | import ".././lib/CurveParams.sol"; 15 | 16 | contract FactoryAddressCheck is Test { 17 | AssimilatorFactory assimilatorFactory; 18 | 19 | function setUp() public { 20 | assimilatorFactory = new AssimilatorFactory(); 21 | } 22 | 23 | function testFailZeroFactoryAddress() public { 24 | assimilatorFactory.setCurveFactory(address(0)); 25 | fail("AssimFactory/curve factory zero address!"); 26 | } 27 | } 28 | 29 | -------------------------------------------------------------------------------- /test/Router.t.sol: -------------------------------------------------------------------------------- 1 | 2 | // SPDX-License-Identifier: UNLICENSED 3 | pragma solidity ^0.8.10; 4 | 5 | import "forge-std/Test.sol"; 6 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 7 | import "@openzeppelin/contracts/utils/math/SafeMath.sol"; 8 | 9 | import "../src/interfaces/IAssimilator.sol"; 10 | import "../src/interfaces/IOracle.sol"; 11 | import "../src/interfaces/IERC20Detailed.sol"; 12 | import "../src/AssimilatorFactory.sol"; 13 | import "../src/CurveFactoryV2.sol"; 14 | import "../src/Curve.sol"; 15 | import "../src/Structs.sol"; 16 | import "../src/Router.sol"; 17 | import "../src/Config.sol"; 18 | import "../src/lib/ABDKMath64x64.sol"; 19 | 20 | import "./lib/MockUser.sol"; 21 | import "./lib/CheatCodes.sol"; 22 | import "./lib/Address.sol"; 23 | import "./lib/CurveParams.sol"; 24 | 25 | contract RouterTest is Test { 26 | using SafeMath for uint256; 27 | 28 | CheatCodes cheats = CheatCodes(HEVM_ADDRESS); 29 | 30 | MockUser multisig; 31 | MockUser[2] public users; 32 | 33 | IERC20Detailed usdc = IERC20Detailed(Mainnet.USDC); 34 | IERC20Detailed cadc = IERC20Detailed(Mainnet.CADC); 35 | IERC20Detailed xsgd = IERC20Detailed(Mainnet.XSGD); 36 | IERC20Detailed euroc = IERC20Detailed(Mainnet.EUROC); 37 | 38 | uint8 constant fxTokenCount = 3; 39 | 40 | IERC20Detailed[] public foreignStables = [ 41 | cadc, 42 | xsgd, 43 | euroc, 44 | usdc 45 | ]; 46 | 47 | IOracle usdcOracle = IOracle(Mainnet.CHAINLINK_USDC_USD); 48 | IOracle cadcOracle = IOracle(Mainnet.CHAINLINK_CAD_USD); 49 | IOracle xsgdOracle = IOracle(Mainnet.CHAINLINK_SGD_USD); 50 | IOracle eurocOracle = IOracle(Mainnet.CHAINLINK_EUR_USD); 51 | 52 | IOracle[] public foreignOracles = [ 53 | cadcOracle, 54 | xsgdOracle, 55 | eurocOracle, 56 | usdcOracle 57 | ]; 58 | 59 | int128 public protocolFee = 50; 60 | 61 | Config config; 62 | AssimilatorFactory assimilatorFactory; 63 | CurveFactoryV2 curveFactory; 64 | Router router; 65 | Curve[fxTokenCount] dfxCurves; 66 | 67 | function setUp() public { 68 | multisig = new MockUser(); 69 | 70 | for (uint8 i = 0; i < users.length; i++) { 71 | users[i] = new MockUser(); 72 | } 73 | 74 | config = new Config(protocolFee,address(multisig)); 75 | 76 | assimilatorFactory = new AssimilatorFactory(); 77 | 78 | curveFactory = new CurveFactoryV2( 79 | address(assimilatorFactory), 80 | address(config) 81 | ); 82 | 83 | router = new Router(address(curveFactory)); 84 | 85 | assimilatorFactory.setCurveFactory(address(curveFactory)); 86 | 87 | cheats.startPrank(address(multisig)); 88 | for (uint8 i = 0; i < fxTokenCount; i++) { 89 | CurveInfo memory curveInfo = CurveInfo( 90 | string.concat("dfx-", foreignStables[i].symbol()), 91 | string.concat("dfx-", foreignStables[i].symbol()), 92 | address(foreignStables[i]), 93 | address(usdc), 94 | DefaultCurve.BASE_WEIGHT, 95 | DefaultCurve.QUOTE_WEIGHT, 96 | foreignOracles[i], 97 | usdcOracle, 98 | DefaultCurve.ALPHA, 99 | DefaultCurve.BETA, 100 | DefaultCurve.MAX, 101 | DefaultCurve.EPSILON, 102 | DefaultCurve.LAMBDA 103 | ); 104 | 105 | dfxCurves[i] = curveFactory.newCurve(curveInfo); 106 | } 107 | cheats.stopPrank(); 108 | 109 | uint256 user1TknAmnt = 300_000_000; 110 | 111 | // Mint Foreign Stables 112 | for (uint8 i = 0; i <= fxTokenCount; i++) { 113 | uint256 decimals = 10 ** foreignStables[i].decimals(); 114 | deal(address(foreignStables[i]), address(users[0]), user1TknAmnt.mul(decimals)); 115 | } 116 | 117 | cheats.startPrank(address(users[0])); 118 | for (uint8 i = 0; i < fxTokenCount; i++) { 119 | foreignStables[i].approve(address(dfxCurves[i]), type(uint).max); 120 | foreignStables[i].approve(address(router), type(uint).max); 121 | usdc.approve(address(dfxCurves[i]), type(uint).max); 122 | } 123 | usdc.approve(address(router), type(uint).max); 124 | cheats.stopPrank(); 125 | 126 | cheats.startPrank(address(users[0])); 127 | for (uint8 i = 0; i < fxTokenCount; i++) { 128 | dfxCurves[i].deposit(100_000_000e18,0,0,type(uint256).max, type(uint256).max, block.timestamp + 60); 129 | } 130 | cheats.stopPrank(); 131 | } 132 | 133 | function routerOriginSwapAndCheck( 134 | IERC20Detailed fromToken, 135 | IERC20Detailed toToken, 136 | IOracle fromOracle, 137 | IOracle toOracle, 138 | uint256 _amount) public { 139 | 140 | uint8 fromDecimals = fromToken.decimals(); 141 | uint8 toDecimals = toToken.decimals(); 142 | 143 | uint256 amount = uint256(_amount).mul(10 ** fromDecimals); 144 | 145 | deal(address(fromToken), address(this), amount); 146 | fromToken.approve(address(router), type(uint).max); 147 | 148 | uint256 beforeAmount = toToken.balanceOf(address(this)); 149 | 150 | router.originSwap(Mainnet.USDC, address(fromToken), address(toToken), amount, 0, block.timestamp + 60); 151 | 152 | uint256 afterAmount = toToken.balanceOf(address(this)); 153 | 154 | // Get oracle rates assuming decimals are equal 155 | uint256 fromRate = uint256(fromOracle.latestAnswer()); 156 | uint256 toRate = uint256(toOracle.latestAnswer()); 157 | 158 | uint256 obtained = afterAmount.sub(beforeAmount); 159 | uint256 expected = amount.mul(fromRate).div(toRate); 160 | 161 | if (fromDecimals <= toDecimals) { 162 | uint8 decimalsDiff = toDecimals - fromDecimals; 163 | expected = expected.mul(10 ** decimalsDiff); 164 | } else { 165 | uint8 decimalsDiff = fromDecimals - toDecimals; 166 | expected = expected.div(10 ** decimalsDiff); 167 | } 168 | 169 | // 99% approximate 170 | assertApproxEqRel(obtained, expected, 0.01e18); 171 | } 172 | 173 | function routerViewTargetSwapAndCheck( 174 | IERC20Detailed fromToken, 175 | IERC20Detailed toToken, 176 | IOracle fromOracle, 177 | IOracle toOracle, 178 | uint256 _amount) public { 179 | 180 | uint8 fromDecimals = fromToken.decimals(); 181 | uint8 toDecimals = toToken.decimals(); 182 | 183 | uint256 amount = uint256(_amount).mul(10 ** toDecimals); 184 | 185 | // Get oracle rates assuming decimals are equal 186 | uint256 fromRate = uint256(fromOracle.latestAnswer()); 187 | uint256 toRate = uint256(toOracle.latestAnswer()); 188 | 189 | uint256 obtained = router.viewTargetSwap(Mainnet.USDC, address(fromToken), address(toToken), amount); 190 | 191 | uint256 expected = amount.mul(toRate).div(fromRate); 192 | 193 | emit log_uint(expected); 194 | 195 | if (fromDecimals <= toDecimals) { 196 | uint8 decimalsDiff = toDecimals - fromDecimals; 197 | expected = expected.div(10 ** decimalsDiff); 198 | } else { 199 | uint8 decimalsDiff = fromDecimals - toDecimals; 200 | expected = expected.mul(10 ** decimalsDiff); 201 | } 202 | 203 | assertApproxEqRel(obtained, expected, 0.01e18); 204 | } 205 | 206 | // TARGET SWAPS 207 | function testCadcToUsdcTargetSwap(uint256 _amount) public { 208 | cheats.assume(_amount > 100); 209 | cheats.assume(_amount < 10_000_000); 210 | 211 | routerViewTargetSwapAndCheck(cadc, usdc, cadcOracle, usdcOracle, _amount); 212 | } 213 | 214 | function testUsdcToCadcTargetSwap(uint256 _amount) public { 215 | cheats.assume(_amount > 100); 216 | cheats.assume(_amount < 10_000_000); 217 | 218 | routerViewTargetSwapAndCheck(usdc, cadc, usdcOracle, cadcOracle, _amount); 219 | } 220 | 221 | function testCadcToXsgdTargetSwap(uint256 _amount) public { 222 | cheats.assume(_amount > 100); 223 | cheats.assume(_amount < 10_000_000); 224 | 225 | routerViewTargetSwapAndCheck(cadc, xsgd, cadcOracle, xsgdOracle, _amount); 226 | } 227 | 228 | function testCadcToEurocTargetSwap(uint256 _amount) public { 229 | cheats.assume(_amount > 100); 230 | cheats.assume(_amount < 10_000_000); 231 | 232 | routerViewTargetSwapAndCheck(cadc, euroc, cadcOracle, eurocOracle, _amount); 233 | } 234 | 235 | function testEurocToXsgdTargetSwap(uint256 _amount) public { 236 | cheats.assume(_amount > 100); 237 | cheats.assume(_amount < 10_000_000); 238 | 239 | routerViewTargetSwapAndCheck(euroc, xsgd, eurocOracle, xsgdOracle, _amount); 240 | } 241 | 242 | function testXsgdToEurocTargetSwap(uint256 _amount) public { 243 | cheats.assume(_amount > 100); 244 | cheats.assume(_amount < 10_000_000); 245 | 246 | routerViewTargetSwapAndCheck(xsgd, euroc, xsgdOracle, eurocOracle, _amount); 247 | } 248 | 249 | function testXsgdToCadcTargetSwap(uint256 _amount) public { 250 | cheats.assume(_amount > 100); 251 | cheats.assume(_amount < 10_000_000); 252 | 253 | routerViewTargetSwapAndCheck(xsgd, cadc, xsgdOracle, cadcOracle, _amount); 254 | } 255 | 256 | function testEurocToCadcTargetSwap(uint256 _amount) public { 257 | cheats.assume(_amount > 100); 258 | cheats.assume(_amount < 10_000_000); 259 | 260 | routerViewTargetSwapAndCheck(euroc, cadc, eurocOracle, cadcOracle, _amount); 261 | } 262 | 263 | // ORIGIN SWAPS 264 | function testCadcToUsdcOriginSwap(uint256 _amount) public { 265 | cheats.assume(_amount > 100); 266 | cheats.assume(_amount < 10_000_000); 267 | 268 | routerOriginSwapAndCheck(cadc, usdc, cadcOracle, usdcOracle, _amount); 269 | } 270 | 271 | function testUsdcToXsgdOriginSwap(uint256 _amount) public { 272 | cheats.assume(_amount > 100); 273 | cheats.assume(_amount < 10_000_000); 274 | 275 | routerOriginSwapAndCheck(usdc, xsgd, usdcOracle, xsgdOracle, _amount); 276 | } 277 | 278 | function testCadcToEursOriginSwap(uint256 _amount) public { 279 | cheats.assume(_amount > 100); 280 | cheats.assume(_amount < 10_000_000); 281 | 282 | routerOriginSwapAndCheck(cadc, euroc, cadcOracle, eurocOracle, _amount); 283 | } 284 | 285 | function testCadcToXsgdOriginSwap(uint256 _amount) public { 286 | cheats.assume(_amount > 10); 287 | cheats.assume(_amount < 10_000_000); 288 | 289 | routerOriginSwapAndCheck(cadc, xsgd, cadcOracle, xsgdOracle, _amount); 290 | } 291 | 292 | function testEurocToXsgdOriginSwap(uint256 _amount) public { 293 | cheats.assume(_amount > 100); 294 | cheats.assume(_amount < 10_000_000); 295 | 296 | routerOriginSwapAndCheck(euroc, xsgd, eurocOracle, xsgdOracle, _amount); 297 | } 298 | 299 | function testEurocToCadcOriginSwap(uint256 _amount) public { 300 | cheats.assume(_amount > 100); 301 | cheats.assume(_amount < 10_000_000); 302 | 303 | routerOriginSwapAndCheck(euroc, cadc, eurocOracle, cadcOracle, _amount); 304 | } 305 | 306 | function testXSGDToEurocOriginSwap(uint256 _amount) public { 307 | cheats.assume(_amount > 100); 308 | cheats.assume(_amount < 10_000_000); 309 | 310 | routerOriginSwapAndCheck(xsgd, euroc, xsgdOracle, eurocOracle, _amount); 311 | } 312 | 313 | function testXSGDToCadcOriginSwap(uint256 _amount) public { 314 | cheats.assume(_amount > 100); 315 | cheats.assume(_amount < 10_000_000); 316 | 317 | routerOriginSwapAndCheck(xsgd, cadc, xsgdOracle, cadcOracle, _amount); 318 | } 319 | 320 | 321 | // Global Transactable State Frozen 322 | function testFail_GlobalFrozenOriginSwap() public { 323 | // Cannot make swaps because global state is frozen 324 | IConfig(address(config)).setGlobalFrozen(true); 325 | routerOriginSwapAndCheck(cadc, euroc, cadcOracle, eurocOracle, 100_000); 326 | } 327 | 328 | function testFail_GlobalFrozenTargetSwap() public { 329 | // Cannot make swaps because global state is frozen 330 | IConfig(address(config)).setGlobalFrozen(true); 331 | routerViewTargetSwapAndCheck(euroc, xsgd, usdcOracle, cadcOracle, 100_000); 332 | } 333 | } 334 | -------------------------------------------------------------------------------- /test/Zap.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.10; 3 | 4 | import "forge-std/Test.sol"; 5 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 6 | import "@openzeppelin/contracts/utils/math/SafeMath.sol"; 7 | 8 | import "../src/interfaces/IAssimilator.sol"; 9 | import "../src/interfaces/IOracle.sol"; 10 | import "../src/interfaces/IERC20Detailed.sol"; 11 | import "../src/AssimilatorFactory.sol"; 12 | import "../src/CurveFactoryV2.sol"; 13 | import "../src/Curve.sol"; 14 | import "../src/Structs.sol"; 15 | import "../src/Config.sol"; 16 | import "../src/lib/ABDKMath64x64.sol"; 17 | import "../src/Zap.sol"; 18 | 19 | import "./lib/MockUser.sol"; 20 | import "./lib/CheatCodes.sol"; 21 | import "./lib/Address.sol"; 22 | import "./lib/CurveParams.sol"; 23 | import "./lib/MockChainlinkOracle.sol"; 24 | import "./lib/MockOracleFactory.sol"; 25 | import "./lib/MockToken.sol"; 26 | 27 | import "./utils/Utils.sol"; 28 | 29 | contract ZapTest is Test { 30 | using SafeMath for uint256; 31 | CheatCodes cheats = CheatCodes(HEVM_ADDRESS); 32 | Utils utils; 33 | 34 | // account order is lp provider, trader, treasury 35 | MockUser[] public accounts; 36 | MockUser public victim; 37 | 38 | MockOracleFactory oracleFactory; 39 | // token order is gold, euroc, cadc, usdc 40 | IERC20Detailed[] public tokens; 41 | IOracle[] public oracles; 42 | Curve[] public curves; 43 | uint256[] public decimals; 44 | 45 | uint256[] public dividends = [20,2]; 46 | 47 | Config config; 48 | CurveFactoryV2 curveFactory; 49 | AssimilatorFactory assimFactory; 50 | 51 | // Zap contract 52 | Zap public zap; 53 | 54 | function setUp() public { 55 | 56 | utils = new Utils(); 57 | // create temp accounts 58 | for(uint256 i = 0; i < 3; ++i){ 59 | accounts.push(new MockUser()); 60 | } 61 | victim = new MockUser(); 62 | // deploy zap contract 63 | zap = new Zap(); 64 | // deploy gold token & init 3 stable coins 65 | MockToken gold = new MockToken(); 66 | tokens.push(IERC20Detailed(address(gold))); 67 | tokens.push(IERC20Detailed(Mainnet.EUROC)); 68 | tokens.push(IERC20Detailed(Mainnet.CADC)); 69 | tokens.push(IERC20Detailed(Mainnet.USDC)); 70 | 71 | // deploy mock oracle factory for deployed token (named gold) 72 | oracleFactory = new MockOracleFactory(); 73 | oracles.push( 74 | oracleFactory.newOracle( 75 | address(tokens[0]), "goldOracle",9, 20000000000 76 | ) 77 | ); 78 | oracles.push(IOracle(Mainnet.CHAINLINK_EUR_USD)); 79 | oracles.push(IOracle(Mainnet.CHAINLINK_CAD_USD)); 80 | oracles.push(IOracle(Mainnet.CHAINLINK_USDC_USD)); 81 | 82 | config = new Config(50000, address(accounts[2])); 83 | // deploy new assimilator factory & curveFactory v2 84 | assimFactory = new AssimilatorFactory(); 85 | curveFactory = new CurveFactoryV2( 86 | address(assimFactory), 87 | address(config) 88 | ); 89 | assimFactory.setCurveFactory(address(curveFactory)); 90 | // now deploy curves 91 | cheats.startPrank(address(accounts[2])); 92 | for(uint256 i = 0; i < 3;++i){ 93 | CurveInfo memory curveInfo = CurveInfo( 94 | string(abi.encode("dfx-curve-",i)), 95 | string(abi.encode("lp-",i)), 96 | address(tokens[i]), 97 | address(tokens[3]), 98 | DefaultCurve.BASE_WEIGHT, 99 | DefaultCurve.QUOTE_WEIGHT, 100 | oracles[i], 101 | oracles[3], 102 | DefaultCurve.ALPHA, 103 | DefaultCurve.BETA, 104 | DefaultCurve.MAX, 105 | DefaultCurve.EPSILON, 106 | DefaultCurve.LAMBDA 107 | ); 108 | Curve _curve = curveFactory.newCurve(curveInfo); 109 | curves.push(_curve); 110 | } 111 | cheats.stopPrank(); 112 | // now mint gold & silver tokens 113 | uint256 mintAmt = 300_000_000_000; 114 | for(uint256 i = 0; i < 4; ++i){ 115 | decimals.push(utils.tenToPowerOf(tokens[i].decimals())); 116 | if(i == 0) { 117 | tokens[0].mint(address(accounts[0]), mintAmt.mul(decimals[i])); 118 | } 119 | else{ 120 | deal(address(tokens[i]), address(accounts[0]), mintAmt.mul(decimals[i])); 121 | } 122 | } 123 | // now approve 124 | cheats.startPrank(address(accounts[0])); 125 | for(uint256 i = 0; i < 3; ++i){ 126 | tokens[i].approve(address(curves[i]), type(uint).max); 127 | tokens[3].approve(address(curves[i]), type(uint).max); 128 | } 129 | // approve for zap 130 | for(uint256 i = 0; i < 4; ++i) { 131 | tokens[i].approve(address(zap), type(uint256).max); 132 | } 133 | cheats.stopPrank(); 134 | 135 | cheats.startPrank(address(victim)); 136 | IERC20Detailed(Mainnet.EUROC).approve(address(zap), type(uint).max); 137 | IERC20Detailed(Mainnet.USDC).approve(address(zap), type(uint).max); 138 | deal(address(Mainnet.EUROC), address(victim), 100_000_000e6); 139 | cheats.stopPrank(); 140 | } 141 | // // test swap of forex stable coin(euroc, cadc) usdc 142 | function testZap(uint256 amt) public { 143 | cheats.assume(amt > 100); 144 | cheats.assume(amt < 10000000); 145 | for(uint256 i = 0; i < 2; ++i){ 146 | // mint token to zapper 147 | deal(address(tokens[i+1]), address(accounts[1]), amt * decimals[i+1]); 148 | 149 | cheats.startPrank(address(accounts[1])); 150 | tokens[i+1].approve(address(curves[i+1]), type(uint).max); 151 | tokens[3].approve(address(curves[i+1]), type(uint).max); 152 | tokens[i+1].approve(address(zap), type(uint).max); 153 | tokens[3].approve(address(zap), type(uint).max); 154 | cheats.stopPrank(); 155 | 156 | // first deposit 157 | cheats.startPrank(address(accounts[0])); 158 | curves[i+1].deposit(1000000000 * 1e18,0,0,type(uint256).max, type(uint256).max, block.timestamp + 60); 159 | cheats.stopPrank(); 160 | 161 | cheats.startPrank(address(accounts[1])); 162 | uint256 originalBaseBal = tokens[i+1].balanceOf(address(accounts[1])); 163 | zap.zapFromBase( 164 | address(curves[i+1]), 165 | originalBaseBal, 166 | block.timestamp + 60, 167 | 0 168 | ); 169 | // now try unzap 170 | IERC20(address(curves[i+1])).approve(address(zap), type(uint256).max); 171 | zap.upzapFromQuote(address(curves[i+1]), curves[i+1].balanceOf(address(accounts[1])), 0, block.timestamp+60); 172 | uint256 currentBaseBal = tokens[i+1].balanceOf(address(accounts[1])); 173 | uint256 currentQuoteBal = tokens[3].balanceOf(address(accounts[1])); 174 | int256 baseUSDPrice = oracles[i+1].latestAnswer(); 175 | int256 quoteUSDPrice = oracles[3].latestAnswer(); 176 | originalBaseBal = originalBaseBal.div(decimals[i+1]); 177 | currentBaseBal = currentBaseBal.div(decimals[i+1]); 178 | currentQuoteBal = currentQuoteBal.div(decimals[3]); 179 | 180 | uint256 originalBaseInUSD = originalBaseBal.mul(uint256(baseUSDPrice)); 181 | uint256 currentBaseInUSD = currentBaseBal.mul(uint256(baseUSDPrice)); 182 | uint256 currentQuoteInUSD = currentQuoteBal.mul(uint256(quoteUSDPrice)); 183 | uint256 currentTotalInUSD = currentBaseInUSD.add(currentQuoteInUSD); 184 | assertApproxEqAbs(originalBaseInUSD, currentTotalInUSD, originalBaseInUSD.div(20)); 185 | tokens[3].transfer(address(accounts[2]), tokens[3].balanceOf(address(accounts[1]))); 186 | cheats.stopPrank(); 187 | } 188 | } 189 | 190 | function test_Unzap() public { 191 | // first LP deposit 192 | cheats.startPrank(address(accounts[0])); 193 | curves[1].deposit(1_000_000_000 * 1e18,0,0,type(uint256).max, type(uint256).max, block.timestamp + 60); 194 | cheats.stopPrank(); 195 | 196 | cheats.startPrank(address(victim)); 197 | 198 | // victim zaps base -> LP 199 | zap.zapFromBase(address(curves[1]), 20_000_000, block.timestamp + 60, 0); 200 | // current base amount 201 | uint256 balanceAfterZap = tokens[1].balanceOf(address(victim)); 202 | IERC20(address(curves[1])).approve(address(zap), type(uint256).max); 203 | // victim unzaps LP -> base 204 | zap.upzapFromQuote(address(curves[1]), curves[1].balanceOf(address(victim)), 19_900_000, block.timestamp + 60); 205 | 206 | uint256 balanceAfterUnzap = tokens[1].balanceOf(address(victim)); 207 | 208 | emit log_named_uint("unzap received base", balanceAfterUnzap - balanceAfterZap); 209 | } 210 | 211 | function testFail_UnzapMinAmountNotMet() public { 212 | // first LP deposit 213 | cheats.startPrank(address(accounts[0])); 214 | curves[1].deposit(1000000000 * 1e18,0,0,type(uint256).max, type(uint256).max, block.timestamp + 60); 215 | cheats.stopPrank(); 216 | 217 | // Second stage 218 | cheats.startPrank(address(victim)); 219 | 220 | // victim zaps base -> LP again 221 | zap.zapFromBase(address(curves[1]), 20_000_000, block.timestamp + 60, 0); 222 | 223 | // current base amount 224 | uint256 secondBalanceAfterZap = tokens[1].balanceOf(address(victim)); 225 | IERC20(address(curves[1])).approve(address(zap), type(uint256).max); 226 | cheats.stopPrank(); 227 | 228 | // front-runner swaps quote -> base via zap 229 | emit log_named_uint("USDC amount of attacker", 230 | tokens[3].balanceOf(address(accounts[1]))); 231 | cheats.startPrank(address(accounts[1])); 232 | 233 | // amount to swap can increase, depending on the alpha/beta parameters 234 | uint256 amount1 = curves[1].originSwap(address(tokens[3]), address(tokens[1]), 200000000000000, 0, block.timestamp + 60); 235 | cheats.stopPrank(); 236 | 237 | // victim unzaps LP -> base, damaged via front-run 238 | cheats.startPrank(address(victim)); 239 | zap.upzapFromQuote(address(curves[1]), curves[1].balanceOf(address(victim)), 19_900_000, block.timestamp+60); 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /test/lib/Address.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.10; 3 | 4 | library Mainnet { 5 | // Tokens 6 | address public constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; 7 | address public constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; 8 | address public constant DFX = 0x888888435FDe8e7d4c54cAb67f206e4199454c60; 9 | address public constant CADC = 0xcaDC0acd4B445166f12d2C07EAc6E2544FbE2Eef; 10 | address public constant EUROC = 0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c; 11 | address public constant XSGD = 0x70e8dE73cE538DA2bEEd35d14187F6959a8ecA96; 12 | address public constant NZDS = 0xDa446fAd08277B4D2591536F204E018f32B6831c; 13 | address public constant RAI = 0x03ab458634910AaD20eF5f1C8ee96F1D6ac54919; 14 | 15 | // Oracles 16 | // 8-decimals 17 | address public constant CHAINLINK_WETH_USD = 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419; 18 | address public constant CHAINLINK_USDC_USD = 0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6; 19 | address public constant CHAINLINK_NZDS_USD = 0x3977CFc9e4f29C184D4675f4EB8e0013236e5f3e; 20 | address public constant CHAINLINK_CAD_USD = 0xa34317DB73e77d453b1B8d04550c44D10e981C8e; 21 | address public constant CHAINLINK_EUR_USD = 0xb49f677943BC038e9857d61E7d053CaA2C1734C1; 22 | address public constant CHAINLINK_SGD_USD = 0xe25277fF4bbF9081C75Ab0EB13B4A13a721f3E13; 23 | 24 | address public constant CHAINLINK_RAI_USD = 0x3147D7203354Dc06D9fd350c7a2437bcA92387a4; // rai decimal is 18 25 | address public constant XSGD_USDC_POOL = 0x2baB29a12a9527a179Da88F422cDaaA223A90bD5; 26 | } 27 | -------------------------------------------------------------------------------- /test/lib/CheatCodes.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.10; 3 | 4 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 5 | 6 | // https://onbjerg.github.io/foundry-book/forge/cheatcodes.html 7 | 8 | interface CheatCodes { 9 | // Set block.timestamp 10 | function warp(uint256) external; 11 | 12 | // Set block.number 13 | function roll(uint256) external; 14 | 15 | // Set block.basefee 16 | function fee(uint256) external; 17 | 18 | // Loads a storage slot from an address 19 | function load(address account, bytes32 slot) external returns (bytes32); 20 | 21 | // Stores a value to an address' storage slot 22 | function store( 23 | address account, 24 | bytes32 slot, 25 | bytes32 value 26 | ) external; 27 | 28 | // Signs data 29 | function sign(uint256 privateKey, bytes32 digest) 30 | external 31 | returns ( 32 | uint8 v, 33 | bytes32 r, 34 | bytes32 s 35 | ); 36 | 37 | // Computes address for a given private key 38 | function addr(uint256 privateKey) external returns (address); 39 | 40 | // Performs a foreign function call via terminal 41 | function ffi(string[] calldata) external returns (bytes memory); 42 | 43 | // Sets the *next* call's msg.sender to be the input address 44 | function prank(address) external; 45 | 46 | // Sets all subsequent calls' msg.sender to be the input address until `stopPrank` is called 47 | function startPrank(address) external; 48 | 49 | // Sets the *next* call's msg.sender to be the input address, and the tx.origin to be the second input 50 | function prank(address, address) external; 51 | 52 | // Sets all subsequent calls' msg.sender to be the input address until `stopPrank` is called, and the tx.origin to be the second input 53 | function startPrank(address, address) external; 54 | 55 | // Resets subsequent calls' msg.sender to be `address(this)` 56 | function stopPrank() external; 57 | 58 | // Sets an address' balance 59 | function deal(address who, uint256 newBalance) external; 60 | 61 | // Sets an address' code 62 | function etch(address who, bytes calldata code) external; 63 | 64 | // Expects an error on next call 65 | function expectRevert(bytes calldata) external; 66 | 67 | function expectRevert(bytes4) external; 68 | 69 | // Record all storage reads and writes 70 | function record() external; 71 | 72 | // Gets all accessed reads and write slot from a recording session, for a given address 73 | function accesses(address) 74 | external 75 | returns (bytes32[] memory reads, bytes32[] memory writes); 76 | 77 | // Prepare an expected log with (bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData). 78 | // Call this function, then emit an event, then call a function. Internally after the call, we check if 79 | // logs were emitted in the expected order with the expected topics and data (as specified by the booleans) 80 | function expectEmit( 81 | bool, 82 | bool, 83 | bool, 84 | bool 85 | ) external; 86 | 87 | // Mocks a call to an address, returning specified data. 88 | // Calldata can either be strict or a partial match, e.g. if you only 89 | // pass a Solidity selector to the expected calldata, then the entire Solidity 90 | // function will be mocked. 91 | function mockCall( 92 | address, 93 | bytes calldata, 94 | bytes calldata 95 | ) external; 96 | 97 | // Clears all mocked calls 98 | function clearMockedCalls() external; 99 | 100 | // Expect a call to an address with the specified calldata. 101 | // Calldata can either be strict or a partial match 102 | function expectCall(address, bytes calldata) external; 103 | 104 | function getCode(string calldata) external returns (bytes memory); 105 | 106 | // Label an address in test traces 107 | function label(address addr, string memory label) external; 108 | 109 | // When fuzzing, generate new inputs if conditional not met 110 | function assume(bool) external; 111 | } 112 | -------------------------------------------------------------------------------- /test/lib/CurveParams.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import "../../src/Structs.sol"; 5 | 6 | library DefaultCurve { 7 | // Default Curve Params 8 | uint256 public constant ALPHA = 5e17; 9 | uint256 public constant BETA = 35e16; 10 | uint256 public constant MAX = 15e16; 11 | uint256 public constant EPSILON = 4e14; 12 | uint256 public constant LAMBDA = 1e18; 13 | 14 | // Weights 15 | uint256 public constant BASE_WEIGHT = 5e17; 16 | uint256 public constant QUOTE_WEIGHT = 5e17; 17 | } 18 | -------------------------------------------------------------------------------- /test/lib/LowGasSafeMath.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.13; 3 | 4 | /// @title Optimized overflow and underflow safe math operations 5 | /// @notice Contains methods for doing math operations that revert on overflow or underflow for minimal gas cost 6 | library LowGasSafeMath { 7 | /// @notice Returns x + y, reverts if sum overflows uint256 8 | /// @param x The augend 9 | /// @param y The addend 10 | /// @return z The sum of x and y 11 | function add(uint256 x, uint256 y) internal pure returns (uint256 z) { 12 | require((z = x + y) >= x); 13 | } 14 | 15 | /// @notice Returns x - y, reverts if underflows 16 | /// @param x The minuend 17 | /// @param y The subtrahend 18 | /// @return z The difference of x and y 19 | function sub(uint256 x, uint256 y) internal pure returns (uint256 z) { 20 | require((z = x - y) <= x); 21 | } 22 | 23 | /// @notice Returns x * y, reverts if overflows 24 | /// @param x The multiplicand 25 | /// @param y The multiplier 26 | /// @return z The product of x and y 27 | function mul(uint256 x, uint256 y) internal pure returns (uint256 z) { 28 | require(x == 0 || (z = x * y) / x == y); 29 | } 30 | 31 | /// @notice Returns x + y, reverts if overflows or underflows 32 | /// @param x The augend 33 | /// @param y The addend 34 | /// @return z The sum of x and y 35 | function add(int256 x, int256 y) internal pure returns (int256 z) { 36 | require((z = x + y) >= x == (y >= 0)); 37 | } 38 | 39 | /// @notice Returns x - y, reverts if overflows or underflows 40 | /// @param x The minuend 41 | /// @param y The subtrahend 42 | /// @return z The difference of x and y 43 | function sub(int256 x, int256 y) internal pure returns (int256 z) { 44 | require((z = x - y) <= x == (y >= 0)); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /test/lib/MockAggregator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.7; 3 | 4 | contract MockAggregator { 5 | int256 internal _answer; 6 | 7 | function setAnswer(int256 _a) external { 8 | _answer = _a; 9 | } 10 | 11 | function latestAnswer() external view returns (int256) { 12 | return _answer; 13 | } 14 | 15 | function latestRoundData() 16 | external 17 | view 18 | returns ( 19 | uint80, 20 | int256, 21 | uint256, 22 | uint256, 23 | uint80 24 | ) 25 | { 26 | return (0, _answer, 0, 0, 0); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /test/lib/MockChainlinkOracle.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import "../../src/interfaces/IOracle.sol"; 5 | 6 | contract MockChainlinkOracle is IOracle { 7 | string public name; 8 | uint8 public override decimals; 9 | string public override description; 10 | address public constant dummyAddress = address(0); 11 | address public immutable underlying; 12 | int256 public immutable price; 13 | 14 | // dummy constructor 15 | constructor( 16 | address _token, 17 | string memory _name, 18 | uint8 _decimals, 19 | int256 _price 20 | ) { 21 | underlying = _token; 22 | name = _name; 23 | decimals = _decimals; 24 | description = string(abi.encodePacked("this is a price feed for ", name)); 25 | price = _price; 26 | } 27 | 28 | function getUnderlyingToken() external view returns (address) { 29 | return underlying; 30 | } 31 | 32 | // override I oracle interface functions 33 | function acceptOwnership() external override {} 34 | 35 | function accessController() external view override returns (address) { 36 | return dummyAddress; 37 | } 38 | 39 | function aggregator() external view override returns (address) { 40 | return dummyAddress; 41 | } 42 | 43 | function confirmAggregator(address _aggregator) external override {} 44 | 45 | function getAnswer(uint256 _roundId) external view override returns (int256) { 46 | return price; 47 | } 48 | 49 | function getRoundData(uint80 _roundId) 50 | external 51 | view 52 | override 53 | returns ( 54 | uint80 roundId, 55 | int256 answer, 56 | uint256 startedAt, 57 | uint256 updatedAt, 58 | uint80 answeredInRound 59 | ) 60 | { 61 | roundId = 0; 62 | answer = 0; 63 | startedAt = 0; 64 | updatedAt = 0; 65 | answeredInRound = 0; 66 | } 67 | 68 | function getTimestamp(uint256 _roundId) external view override returns (uint256) { 69 | return block.timestamp; 70 | } 71 | 72 | function latestAnswer() external view override returns (int256) { 73 | return price; 74 | } 75 | 76 | function latestRound() external view override returns (uint256) { 77 | return 0; 78 | } 79 | 80 | function latestRoundData() 81 | external 82 | view 83 | override 84 | returns ( 85 | uint80 roundId, 86 | int256 answer, 87 | uint256 startedAt, 88 | uint256 updatedAt, 89 | uint80 answeredInRound 90 | ) 91 | { 92 | roundId = 0; 93 | answer = price; 94 | startedAt = 0; 95 | updatedAt = 0; 96 | answeredInRound = 0; 97 | } 98 | 99 | function latestTimestamp() external view override returns (uint256) { 100 | return 0; 101 | } 102 | 103 | function owner() external view override returns (address) { 104 | return dummyAddress; 105 | } 106 | 107 | function phaseAggregators(uint16) external view override returns (address) { 108 | return dummyAddress; 109 | } 110 | 111 | function phaseId() external view override returns (uint16) { 112 | return 0; 113 | } 114 | 115 | function proposeAggregator(address _aggregator) external override {} 116 | 117 | function proposedAggregator() external view override returns (address) { 118 | return dummyAddress; 119 | } 120 | 121 | function proposedGetRoundData(uint80 _roundId) 122 | external 123 | view 124 | override 125 | returns ( 126 | uint80 roundId, 127 | int256 answer, 128 | uint256 startedAt, 129 | uint256 updatedAt, 130 | uint80 answeredInRound 131 | ) 132 | { 133 | roundId = 0; 134 | answer = price; 135 | startedAt = 0; 136 | updatedAt = 0; 137 | answeredInRound = 0; 138 | } 139 | 140 | function proposedLatestRoundData() 141 | external 142 | view 143 | override 144 | returns ( 145 | uint80 roundId, 146 | int256 answer, 147 | uint256 startedAt, 148 | uint256 updatedAt, 149 | uint80 answeredInRound 150 | ) 151 | { 152 | roundId = 0; 153 | answer = price; 154 | startedAt = 0; 155 | updatedAt = 0; 156 | answeredInRound = 0; 157 | } 158 | 159 | function setController(address _accessController) external override {} 160 | 161 | function transferOwnership(address _to) external override {} 162 | 163 | function version() external view override returns (uint256) { 164 | return 0; 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /test/lib/MockOracleFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.7; 3 | 4 | import "./MockChainlinkOracle.sol"; 5 | 6 | contract MockOracleFactory { 7 | mapping(bytes32 => address) public oracles; 8 | 9 | function newOracle( 10 | address _token, 11 | string memory _name, 12 | uint8 _decimals, 13 | int256 _price 14 | ) external returns (MockChainlinkOracle) { 15 | bytes32 oracleID = keccak256(abi.encode(_token)); 16 | if (oracles[oracleID] != address(0)) { 17 | return MockChainlinkOracle(oracles[oracleID]); 18 | } else { 19 | MockChainlinkOracle _oracle = new MockChainlinkOracle(_token, _name, _decimals, _price); 20 | oracles[oracleID] = address(_oracle); 21 | return _oracle; 22 | } 23 | } 24 | 25 | function getOracle(address _underlying) external view returns (address) { 26 | return oracles[keccak256(abi.encode(_underlying))]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /test/lib/MockToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.10; 3 | 4 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 5 | 6 | contract MockToken is ERC20 { 7 | constructor() ERC20("Mock", "MOCK") {} 8 | 9 | function mint(address _a, uint256 _b) public { 10 | _mint(_a, _b); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/lib/MockUser.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.10; 3 | 4 | contract MockUser { 5 | constructor() {} 6 | 7 | receive() external payable {} 8 | 9 | function call(address _a, bytes memory _b) public payable { 10 | (bool a, ) = _a.call{value: msg.value}(_b); 11 | require(a, "fail"); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/utils/CurveFlash.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.13; 2 | 3 | import "forge-std/Test.sol"; 4 | import "../../src/Curve.sol"; 5 | import "../../src/interfaces/ICurve.sol"; 6 | import "../../src/interfaces/IFlashCallback.sol"; 7 | import "../../src/interfaces/IERC20Detailed.sol"; 8 | import "../lib/Address.sol"; 9 | import '../lib/LowGasSafeMath.sol'; 10 | import "./FlashStructs.sol"; 11 | import "./Utils.sol"; 12 | 13 | contract CurveFlash is IFlashCallback, Test { 14 | using LowGasSafeMath for uint256; 15 | using LowGasSafeMath for int256; 16 | using SafeERC20 for IERC20; 17 | 18 | Curve public dfxCurve; 19 | Utils utils; 20 | 21 | function flashCallback( 22 | uint256 fee0, 23 | uint256 fee1, 24 | bytes calldata data 25 | ) external override { 26 | FlashCallbackData memory decoded = abi.decode(data, (FlashCallbackData)); 27 | 28 | address curve = decoded.poolAddress; 29 | 30 | address token0 = ICurve(curve).derivatives(0); 31 | address token1 = ICurve(curve).derivatives(1); 32 | 33 | // Ensure flashed tokens exist 34 | assertEq(IERC20(token0).balanceOf(address(this)), uint256(100_000).mul(decoded.decimal0).add(decoded.amount0)); 35 | assertEq(IERC20(token1).balanceOf(address(this)), uint256(100_000).mul(decoded.decimal1).add(decoded.amount1)); 36 | 37 | uint256 amount0Owed = LowGasSafeMath.add(decoded.amount0, fee0); 38 | uint256 amount1Owed = LowGasSafeMath.add(decoded.amount1, fee1); 39 | 40 | IERC20(token0).safeTransfer(decoded.poolAddress, amount0Owed); 41 | IERC20(token1).safeTransfer(decoded.poolAddress, amount1Owed); 42 | } 43 | 44 | function initFlash(address _dfxCurve, FlashParams memory params) external { 45 | dfxCurve = Curve(_dfxCurve); 46 | 47 | dfxCurve.flash( 48 | address(this), 49 | params.amount0, 50 | params.amount1, 51 | abi.encode( 52 | FlashCallbackData({ 53 | amount0: params.amount0, 54 | amount1: params.amount1, 55 | decimal0: params.decimal0, 56 | decimal1: params.decimal1, 57 | poolAddress: _dfxCurve 58 | }) 59 | ) 60 | ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /test/utils/CurveFlashReentrancy.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.13; 2 | 3 | import "forge-std/Test.sol"; 4 | import "../../src/Curve.sol"; 5 | import "../../src/interfaces/ICurve.sol"; 6 | import "../../src/interfaces/IFlashCallback.sol"; 7 | import "../../src/interfaces/IERC20Detailed.sol"; 8 | import "../lib/Address.sol"; 9 | import '../lib/LowGasSafeMath.sol'; 10 | import "./FlashStructs.sol"; 11 | import "./Utils.sol"; 12 | 13 | contract CurveFlashReentrancy is IFlashCallback, Test { 14 | using LowGasSafeMath for uint256; 15 | using LowGasSafeMath for int256; 16 | using SafeERC20 for IERC20; 17 | 18 | Curve public dfxCurve; 19 | Utils utils; 20 | 21 | function flashCallback( 22 | uint256 fee0, 23 | uint256 fee1, 24 | bytes calldata data 25 | ) external override { 26 | FlashCallbackData memory decoded = abi.decode(data, (FlashCallbackData)); 27 | 28 | address curve = decoded.poolAddress; 29 | 30 | address token0 = ICurve(curve).derivatives(0); 31 | address token1 = ICurve(curve).derivatives(1); 32 | 33 | IERC20(token0).approve(address(curve), type(uint256).max); 34 | IERC20(token1).approve(address(curve), type(uint256).max); 35 | 36 | // Ensure flashed tokens exist 37 | assertEq(IERC20(token0).balanceOf(address(this)), uint256(100_000).mul(decoded.decimal0).add(decoded.amount0)); 38 | assertEq(IERC20(token1).balanceOf(address(this)), uint256(100_000).mul(decoded.decimal1).add(decoded.amount1)); 39 | 40 | uint256 amount0Owed = LowGasSafeMath.add(decoded.amount0, fee0); 41 | uint256 amount1Owed = LowGasSafeMath.add(decoded.amount1, fee1); 42 | 43 | // Reentrancy here 44 | // Need to deposit more because of the fee 45 | ICurve(curve).deposit(110_000e18, block.timestamp + 1); 46 | } 47 | 48 | function initFlash(address _dfxCurve, FlashParams memory params) external { 49 | dfxCurve = Curve(_dfxCurve); 50 | 51 | dfxCurve.flash( 52 | address(this), 53 | params.amount0, 54 | params.amount1, 55 | abi.encode( 56 | FlashCallbackData({ 57 | amount0: params.amount0, 58 | amount1: params.amount1, 59 | decimal0: params.decimal0, 60 | decimal1: params.decimal1, 61 | poolAddress: _dfxCurve 62 | }) 63 | ) 64 | ); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /test/utils/FlashStructs.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | struct FlashParams { 5 | address token0; 6 | address token1; 7 | uint256 amount0; 8 | uint256 amount1; 9 | uint256 decimal0; 10 | uint256 decimal1; 11 | } 12 | 13 | struct FlashCallbackData { 14 | uint256 amount0; 15 | uint256 amount1; 16 | uint256 decimal0; 17 | uint256 decimal1; 18 | address poolAddress; 19 | } 20 | -------------------------------------------------------------------------------- /test/utils/Utils.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.10; 3 | 4 | contract Utils { 5 | function tenToPowerOf(uint256 decimals) public pure returns (uint256 pow){ 6 | if (decimals == 2) { 7 | return 1e2; 8 | } else if (decimals == 8) { 9 | return 1e8; 10 | } 11 | else if (decimals == 6) { 12 | return 1e6; 13 | } else if (decimals == 18) { 14 | return 1e18; 15 | } else if(decimals == 0) { 16 | return 1; 17 | } else return 0; 18 | } 19 | } 20 | --------------------------------------------------------------------------------