├── .gitignore ├── LICENSE ├── README.OLD.md ├── README.md ├── TODO ├── addresses ├── euler-addresses-goerli.json ├── euler-addresses-mainnet.json └── euler-addresses-ropsten.json ├── contracts ├── Base.sol ├── BaseIRM.sol ├── BaseIRMLinearKink.sol ├── BaseLogic.sol ├── BaseModule.sol ├── Constants.sol ├── Euler.sol ├── Events.sol ├── IRiskManager.sol ├── Interfaces.sol ├── PToken.sol ├── Proxy.sol ├── Storage.sol ├── Utils.sol ├── adaptors │ └── FlashLoan.sol ├── mining │ ├── EulDistributor.sol │ ├── EulDistributorOwner.sol │ └── EulStakes.sol ├── modules │ ├── DToken.sol │ ├── EToken.sol │ ├── Exec.sol │ ├── Governance.sol │ ├── Installer.sol │ ├── Liquidation.sol │ ├── Markets.sol │ ├── RiskManager.sol │ ├── Swap.sol │ ├── SwapHub.sol │ └── interest-rate-models │ │ ├── IRMClassLido.sol │ │ ├── IRMClassMajor.sol │ │ ├── IRMClassMega.sol │ │ ├── IRMClassMidCap.sol │ │ ├── IRMClassOHM.sol │ │ ├── IRMClassStable.sol │ │ ├── IRMClassUSDT.sol │ │ ├── IRMDefault.sol │ │ └── test │ │ ├── IRMFixed.sol │ │ ├── IRMLinear.sol │ │ └── IRMZero.sol ├── oracles │ ├── WBTCOracle.sol │ ├── WSTETHOracle.sol │ └── chainlinkBasedOracle.sol ├── swapHandlers │ ├── ISwapHandler.sol │ ├── SwapHandler1Inch.sol │ ├── SwapHandlerBase.sol │ ├── SwapHandlerCombinedBase.sol │ ├── SwapHandlerUniAutoRouter.sol │ └── SwapHandlerUniswapV3.sol ├── test │ ├── FlashLoanAdaptorTest.sol │ ├── FlashLoanAdaptorTest2.sol │ ├── FlashLoanNativeTest.sol │ ├── InvariantChecker.sol │ ├── JunkETokenUpgrade.sol │ ├── JunkMarketsUpgrade.sol │ ├── MockEACAggregatorProxy.sol │ ├── MockStETH.sol │ ├── MockUniswapV3Factory.sol │ ├── MockUniswapV3Pool.sol │ ├── SimpleUniswapPeriphery.sol │ ├── TestERC20.sol │ └── TestModule.sol ├── vendor │ ├── FullMath.sol │ ├── ISwapRouter02.sol │ ├── ISwapRouterV2.sol │ ├── ISwapRouterV3.sol │ ├── IUniswapV3SwapCallback.sol │ ├── MerkleProof.sol │ ├── RPow.sol │ └── TickMath.sol └── views │ ├── EulerGeneralView.sol │ └── EulerSimpleLens.sol ├── docs ├── attacks.md ├── audit.md ├── custom_environments.md ├── governance-action-validaton.md ├── invariants.md ├── limits.md ├── liquidations.md ├── re-entrancy.md ├── self-collateralisation.md ├── sub-accounts.md ├── tasks.md └── verification.md ├── hardhat.config.js ├── package-lock.json ├── package.json ├── scripts ├── calculate-irm-linear-kink.js ├── dev-setup.js ├── irm-sim.js ├── mint-tokens.sh ├── prod-setup.js ├── render-docs.pl ├── set-gov-admin.js ├── staging-setup.js ├── staging-sim.js └── templates │ ├── IEuler.sol.tt │ └── contract-reference.md.tt ├── tasks ├── batch.js ├── compileoverride.js ├── debug.js ├── euler.js ├── gov.js ├── liquidate.js ├── module.js ├── permit.js ├── staging.js ├── testoverride.js ├── testtoken.js ├── uniswap.js ├── verification.js └── view.js └── test ├── WBTCOracle-integration.js ├── activateMarket.js ├── amountLimits.js ├── averageLiquidity.js ├── balancesNoInterest.js ├── balancesWithInterest.js ├── batch.js ├── borrowBasic.js ├── borrowIsolation.js ├── burn.js ├── chainlinkBasedOracles-integration.js ├── chainlinkBasedOracles.js ├── chainlinkPriceFeed-integration.js ├── chainlinkPriceFeed.js ├── decimals.js ├── decimalsAbove18.js ├── demotedAssets.js ├── enterExitMarkets.js ├── eulDistributor.js ├── eulStakes.js ├── flashLoanAdaptor.js ├── flashLoanNative.js ├── gas.js ├── governorAdmin.js ├── irChanges.js ├── irmClassLido-integration.js ├── irmLinearKink.js ├── lib ├── 1inch-payloads.json ├── 1inch-swaphub-payloads.json ├── deployLib.js ├── eTestLib.js ├── merkle-tree.js ├── scenarios.js ├── sqrtPriceUtils.js ├── token-setups │ ├── goerli.js │ ├── kovan.js │ ├── mainnet-fork.js │ ├── mainnet.js │ ├── ropsten.js │ ├── staging.js │ ├── testing-real-uniswap-activated.js │ ├── testing-real-uniswap.js │ └── testing.js └── uniswap-payloads.json ├── liquidation.js ├── liquidity.js ├── maliciousToken.js ├── marketViews.js ├── mintBurn.js ├── miscellaneous.js ├── moduleUpgrade.js ├── pToken.js ├── permit-integration.js ├── permit.js ├── rebasing.js ├── reserves.js ├── reservesDonate.js ├── reservesInitial.js ├── selfApproveDToken.js ├── selfApproveEToken.js ├── selfCollateralisation.js ├── simpleLens.js ├── storage.js ├── swap1inch-integration.js ├── swapHub1inch-integration.js ├── swapHubRepayUni3.js ├── swapHubUni3.js ├── swapHubUniAutoRouter-integration.js ├── swapRepay.js ├── swapUni3.js ├── tokensMisc.js ├── transferDToken.js ├── transferEToken.js ├── twap.js ├── uniswap-twap.js ├── vendor-artifacts ├── EACAggregatorProxy.json ├── SwapRouter02.json ├── SwapRouterV3.json ├── UniswapV3Factory.json └── UniswapV3Pool.json ├── view.js ├── wstETHOracle-integration.js └── wstETHOracle.js /.gitignore: -------------------------------------------------------------------------------- 1 | /euler-addresses.json 2 | /euler-addresses-*.json 3 | /node_modules/ 4 | /artifacts/ 5 | /cache/ 6 | /.idea/ 7 | /coverage.json 8 | /coverage/ 9 | .DS_Store 10 | .env 11 | .env.* 12 | notes.txt 13 | detect-permit-errors.log 14 | accountsAtRiskOfViolation.json 15 | accountsInViolation.json -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Business Source License 1.1 2 | 3 | License text copyright (c) 2017 MariaDB Corporation Ab, All Rights Reserved. 4 | "Business Source License" is a trademark of MariaDB Corporation Ab. 5 | 6 | ----------------------------------------------------------------------------- 7 | 8 | Parameters 9 | 10 | Licensor: Euler XYZ Ltd. 11 | 12 | Licensed Work: Euler Contracts 13 | The Licensed Work is (c) 2021 Euler XYZ Ltd. 14 | 15 | Additional Use Grant: None 16 | 17 | Change Date: 2023-12-13 18 | 19 | Change License: GNU General Public License v2.0 or later 20 | 21 | ----------------------------------------------------------------------------- 22 | 23 | Terms 24 | 25 | The Licensor hereby grants you the right to copy, modify, create derivative 26 | works, redistribute, and make non-production use of the Licensed Work. The 27 | Licensor may make an Additional Use Grant, above, permitting limited 28 | production use. 29 | 30 | Effective on the Change Date, or the fourth anniversary of the first publicly 31 | available distribution of a specific version of the Licensed Work under this 32 | License, whichever comes first, the Licensor hereby grants you rights under 33 | the terms of the Change License, and the rights granted in the paragraph 34 | above terminate. 35 | 36 | If your use of the Licensed Work does not comply with the requirements 37 | currently in effect as described in this License, you must purchase a 38 | commercial license from the Licensor, its affiliated entities, or authorized 39 | resellers, or you must refrain from using the Licensed Work. 40 | 41 | All copies of the original and modified Licensed Work, and derivative works 42 | of the Licensed Work, are subject to this License. This License applies 43 | separately for each version of the Licensed Work and the Change Date may vary 44 | for each version of the Licensed Work released by Licensor. 45 | 46 | You must conspicuously display this License on each original or modified copy 47 | of the Licensed Work. If you receive the Licensed Work in original or 48 | modified form from a third party, the terms and conditions set forth in this 49 | License apply to your use of that work. 50 | 51 | Any use of the Licensed Work in violation of this License will automatically 52 | terminate your rights under this License for the current and all other 53 | versions of the Licensed Work. 54 | 55 | This License does not grant you any right in any trademark or logo of 56 | Licensor or its affiliates (provided that you may use a trademark or logo of 57 | Licensor as expressly required by this License). 58 | 59 | TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON 60 | AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, 61 | EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF 62 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND 63 | TITLE. 64 | 65 | MariaDB hereby grants you permission to use this License’s text to license 66 | your works, and to refer to it using the trademark "Business Source License", 67 | as long as you comply with the Covenants of Licensor below. 68 | 69 | ----------------------------------------------------------------------------- 70 | 71 | Covenants of Licensor 72 | 73 | In consideration of the right to use this License’s text and the "Business 74 | Source License" name and trademark, Licensor covenants to MariaDB, and to all 75 | other recipients of the licensed work to be provided by Licensor: 76 | 77 | 1. To specify as the Change License the GPL Version 2.0 or any later version, 78 | or a license that is compatible with GPL Version 2.0 or a later version, 79 | where "compatible" means that software provided under the Change License can 80 | be included in a program with software provided under GPL Version 2.0 or a 81 | later version. Licensor may specify additional Change Licenses without 82 | limitation. 83 | 84 | 2. To either: (a) specify an additional grant of rights to use that does not 85 | impose any additional restriction on the right granted in this License, as 86 | the Additional Use Grant; or (b) insert the text "None". 87 | 88 | 3. To specify a Change Date. 89 | 90 | 4. Not to modify this License in any other way. 91 | 92 | ----------------------------------------------------------------------------- 93 | 94 | Notice 95 | 96 | The Business Source License (this document, or the "License") is not an Open 97 | Source license. However, the Licensed Work will eventually be made available 98 | under an Open Source License, as stated in this License. 99 | -------------------------------------------------------------------------------- /README.OLD.md: -------------------------------------------------------------------------------- 1 | # Euler Smart Contracts 2 | 3 | This repo contains the smart contracts and tests for the [Euler Protocol](https://www.euler.finance/). 4 | 5 | ## Setup 6 | 7 | npm i 8 | 9 | ## Testing 10 | 11 | npx hardhat test 12 | 13 | ## Generate coverage report 14 | 15 | npx hardhat coverage 16 | 17 | ## Docs 18 | 19 | * [General Euler Docs](https://docs.euler.finance/) 20 | * [Contract Architecturel](https://docs.euler.finance/developers/getting-started/architecture) 21 | * [Contract Reference](https://docs.euler.finance/developers/getting-started/contract-reference) 22 | * [IEuler.sol Solidity Interface](https://github.com/euler-xyz/euler-interfaces/blob/master/contracts/IEuler.sol) 23 | 24 | ## License 25 | 26 | All files are licensed under GPL-2.0 or later except for the following, which are licensed under Business Source License 1.1 (see the file `LICENSE`): 27 | 28 | * `contracts/modules/RiskManager.sol` 29 | * `contracts/modules/Liquidation.sol` 30 | 31 | These two files will be automatically re-licensed under GPL-2.0 on December 13th, 2023. 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Old Euler Smart Contracts 2 | 3 | These are the contracts for the old Euler V1 system. They are considered deprecated and are left here for historical reasons. 4 | 5 | If you are looking for our next generation system, please see the following: 6 | 7 | * *Ethereum Vault Connector*: [website](https://evc.wtf/), [repo](https://github.com/euler-xyz/ethereum-vault-connector), [whitepaper](https://github.com/euler-xyz/ethereum-vault-connector/blob/master/docs/whitepaper.md) 8 | * *Euler Vault Kit*: [repo](https://github.com/euler-xyz/euler-vault-kit), [whitepaper](https://github.com/euler-xyz/euler-vault-kit/blob/master/docs/whitepaper.md) 9 | * *Euler Price Oracle*: [repo](https://github.com/euler-xyz/euler-price-oracle), [whitepaper](https://github.com/euler-xyz/euler-price-oracle/blob/master/docs/whitepaper.md) 10 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | Lending logic: 2 | g when a token has < 18 decimal places, and a user withdraws their full EToken balance, 0 out the remaining dust so user gets a storage refund 3 | 4 | Exec 5 | ? in batch dispatch, returning values probably doesn't make sense. use logs instead 6 | * tests 7 | * test that internal modules cannot be called through dispatcher or batch dispatch 8 | * failure cases in batch 9 | 10 | RiskManager: 11 | ? how to determine which uniswap pool (fee-level) to use when market activated? check liquidity? 12 | 13 | Liquidation 14 | * self-collateralisation 15 | ? expose bonus breakdown via LiqOpp 16 | ? if health score would decrease post liquidation, allow entire position to be liquidated 17 | 18 | Tokens 19 | ? implement permit() on E/DTokens 20 | 21 | Invariants to add to InvariantChecker.sol contract 22 | * If a user has a non-zero borrow owed: 23 | * they must be entered into market 24 | * must have a non-zero interest accumulator 25 | * If totalBorrows > 0, must have a non-zero interest accumulator 26 | 27 | Pre-release checklist 28 | * Make sure nothing is done in module constructors except set immutable variables, and modules have no storage variables 29 | * All methods that take 2 addresses (liquidations/transfers/approvals) should verify they aren't == or sub-accounts 30 | * Verify every external/public, non-view/pure function in a module is marked nonReentrant 31 | * Some functions don't need reentrancy guard, ie: approve(), tag them reentrantOK 32 | * No accidental external methods inherited (nothing external/public in the Base classes, except moduleId) 33 | * Storage layout consistent between releases 34 | * Make sure "msg.sender" not present in contracts/modules/ 35 | * Any path to increase/decrease/transfer an account's balance/borrow should do an updateAverageLiquidity() first 36 | -------------------------------------------------------------------------------- /addresses/euler-addresses-goerli.json: -------------------------------------------------------------------------------- 1 | { 2 | "tokens": {}, 3 | "modules": { 4 | "installer": "0xEFE9459011e60239d47319Be7E689056466afd3C", 5 | "markets": "0xeE28839fde1e462C8e0b80DE618cB98dB3c017F8", 6 | "liquidation": "0x849f5CC81d12887BC0e4e42D8C87AbA896bDCAC0", 7 | "governance": "0xc9314acCF0d3754A38DdE280D24c51D280C33D16", 8 | "exec": "0x6C933044542d6cAF8927E516B9A99632697bD4C0", 9 | "eToken": "0xbCC46Cf2E9e3CC92Cdd20442059A77e9Cd261902", 10 | "dToken": "0xB873757f203C28D37220B771f493F1c278c9CE59", 11 | "riskManager": "0x3b8eCB5eeA7Cc526212C0730e884e75eb68955d1", 12 | "irmZero": "0x9Dcf4a4597ee6Dcfb9DFDB4739CE69923c2abd7A", 13 | "irmFixed": "0xBCe59050DDe19C3cB87ad299CF22E91cE731aFfB", 14 | "irmLinear": "0x5B574D56A284596a994b58A04617CEc059aCbE1D" 15 | }, 16 | "chainlinkCompatOracles": { 17 | "UNI": "0x8039102cE7E5fa49798f11530368301Ffa5Ae650", 18 | "USDC": "0x362a26a19466b3B9962e223A0733E21dfF79166E", 19 | "USDT": "0x55C34D69166c9Cb91BBa5Ad02f4fE54F01c29a4c", 20 | "DOGE": "0xDd501690aC234A373f35e3Eff7A38116386be789", 21 | "WBTC": "0x9b4FA47152593D99a34E451E00CAccabAa7850A7", 22 | "COMP": "0x2e45E4B2d6CBBAd2E3D576dFa04662778d745cA0", 23 | "CRV": "0x87351b560ebc1810CF33cBA7A0b508e2Cf36e821" 24 | }, 25 | "eulerGeneralView": "0x486492546998494C224b294aE8c5a2982946220B", 26 | "euler": "0x931172BB95549d0f29e10ae2D079ABA3C63318B3", 27 | "installer": "0xB21fb96025c2b7F618ba9D3ae955D20Fa303b2D2", 28 | "markets": "0x3EbC39b84B1F856fAFE9803A9e1Eae7Da016Da36", 29 | "liquidation": "0x66326c072283feE63E1C3feF9BD024F8697EC1BB", 30 | "governance": "0x496A8344497875D0D805167874f2f938aEa15600", 31 | "exec": "0x4b62EB6797526491eEf6eF36D3B9960E5d66C394" 32 | } 33 | -------------------------------------------------------------------------------- /addresses/euler-addresses-mainnet.json: -------------------------------------------------------------------------------- 1 | { 2 | "tokens": {}, 3 | "modules": { 4 | "installer": "0xeC29b4C2CaCaE5dF1A491f084E5Ec7C62A7EdAb5", 5 | "markets": "0x1E21CAc3eB590a5f5482e1CCe07174DcDb7f7FCe", 6 | "liquidation": "0xd737eE2bB39F49C62a436002A77f2710cc45eD98", 7 | "governance": "0xe83B69EdfD12e477fe47152C0e1D65576B91A1cf", 8 | "exec": "0x0Bf04952a5b3eF6bAD343C2218F584a7413bb44d", 9 | "eToken": "0xbb0D4bb654a21054aF95456a3B29c63e8D1F4c0a", 10 | "dToken": "0x29DaDdfdA3442693c21A50351a2B4820DDbBFF79", 11 | "riskManager": "0x3297c8db9360f87a7f7826f52A4FA143988931a6", 12 | "irmDefault": "0x68004911694EC42f0c56B7144a6A5281fFDD38F7", 13 | "irmClassMajor": "0xD75870dCbd1521E6CAd7566FbcA35D72e238572B", 14 | "irmClassMega": "0x894c7499F240c0E0205c56d26A5D609C8408De2D", 15 | "irmClassMidCap": "0xf058fE816B01265d1BA0A3bD226049676b107318", 16 | "irmClassStable": "0x42ec0eb1d2746A9f2739D7501C5d5608bdE9eE89", 17 | "irmClassLido": "0x9053388bDa7cA8A31763E3341Bcb1E4b3E54C5F3", 18 | "irmClassUSDT": "0xFD310B00523707e6b3A9Ba83c3D6EAC3350CF8eA", 19 | "irmClassOHM": "0x89e8f69c302D352D37De10019a69Df4F7F959Bef", 20 | "swap": "0xE96C9749c63cA406C8BAF800c3d101C9701945Dc", 21 | "swapHub": "0xC57D9e4a688551d99645B49126e93589d4b478c8" 22 | }, 23 | "chainlinkCompatOracles": { 24 | "WSTETH": "0x6061b6Be1b5922406D674c57d2ce553F2454523E", 25 | "MATIC": "0x4f8E6BBF4637CCd859CaA9Cc88fCaCAC05A5d251", 26 | "ENS": "0x56D59F0A1dFB41C1de3cf0EC91CB4308B76d079b", 27 | "MIM": "0x82667Bf55FeBCdF88f617855b01A30A67b12322c", 28 | "IMX": "0x5945ae3d5beF0dc695F32FC91f006f2d2D7eaEe1", 29 | "LUSD": "0x816ED6DFc4a916f7ff3aeBe2CBd8f69068f471ec", 30 | "REQ": "0xE513452372167B3401a300834283f12fB0784758", 31 | "FXS": "0x7528a0DaA9026ea196933538Ad09555A77080b1a", 32 | "WBTC": "0xc87BFC9DE10B45ADB6D10945F612a3DE5B3E134E", 33 | "XCN": "0x9e82C6a347c5444099DDb1E0B501bbeA1e22C7F0" 34 | }, 35 | "swapHandlers": { 36 | "SwapHandler1Inch": "0x32673dAA164F4a290AE1f75DDc6aea5A2d343065", 37 | "SwapHandlerUniAutoRouter": "0xD1679029Ae81DA676d9b5850feb6C8EE68f9d04E", 38 | "SwapHandlerUniswapV3": "0x7527E082300fb8D189B3c07dB3BEcc990B5037E7" 39 | }, 40 | "eulerGeneralView": "0xACC25c4d40651676FEEd43a3467F3169e3E68e42", 41 | "eulerSimpleLens": "0x5077B7642abF198b4a5b7C4BdCE4f03016C7089C", 42 | "euler": "0x27182842E098f60e3D576794A5bFFb0777E025d3", 43 | "installer": "0x055DE1CCbCC9Bc5291569a0b6aFFdF8b5707aB16", 44 | "markets": "0x3520d5a913427E6F0D6A83E07ccD4A4da316e4d3", 45 | "liquidation": "0xf43ce1d09050BAfd6980dD43Cde2aB9F18C85b34", 46 | "governance": "0xAF68CFba29D0e15490236A5631cA9497e035CD39", 47 | "exec": "0x59828FdF7ee634AaaD3f58B19fDBa3b03E2D9d80", 48 | "flashLoan": "0x07df2ad9878F8797B4055230bbAE5C808b8259b3", 49 | "swap": "0x7123C8cBBD76c5C7fCC9f7150f23179bec0bA341", 50 | "eulStakes": "0xc697BB6625D9f7AdcF0fbf0cbd4DcF50D8716cd3", 51 | "eulDistributor": "0xd524E29E3BAF5BB085403Ca5665301E94387A7e2", 52 | "swapHub": "0x542ACC8E1db037d6008587aBfB1B7fB44014c629" 53 | } 54 | -------------------------------------------------------------------------------- /addresses/euler-addresses-ropsten.json: -------------------------------------------------------------------------------- 1 | { 2 | "tokens": {}, 3 | "modules": { 4 | "installer": "0x123708B745f8979e2BE846ECBD3E61D986485ea5", 5 | "markets": "0x7cf6Daa8923383500325D6E05554b673fdaaFf76", 6 | "liquidation": "0x5BB1B5DB288A81c97DB5b9513a6e71Da25136c28", 7 | "governance": "0x83700f43C9Cf7cf6A1714641e6EB02c848BaaD77", 8 | "exec": "0x8233F64F130724118D41bf2712FD6143F654d1c8", 9 | "eToken": "0xfdBA9598350fc825241781b8Cb8d58EdCbc12643", 10 | "dToken": "0x0EfD255f10A4cA9b7A6BCaf36695Cb31b1Df7164", 11 | "riskManager": "0xaD3f59fCD897cBAAD5084B05Dff63eEF40d30723", 12 | "irmDefault": "0xd26285F0237EbD3316B555c6d5Ef32c6D1F66a3f", 13 | "irmClassMega": "0xf6a0d1aeD7adB83028D164e1E28C26E627946f9d", 14 | "irmClassLido": "0x1F24bD33f3E7B026CE8081F54281602505d4513e", 15 | "swap": "0xFB324675E09F8e26B643BCB5e6a8A5cf4c97ff2e" 16 | }, 17 | "eulerGeneralView": "0x949816496AEdE5400D8cC90cEf10225862F2f652", 18 | "eulerSimpleLens": "0xc9889EbE7730f23b489752dFEE553749DB2DAA93", 19 | "euler": "0xfC3DD73e918b931be7DEfd0cc616508391bcc001", 20 | "installer": "0xd29B4b18e689fbd474912C75CF65B08BbC998E05", 21 | "markets": "0x60Ec84902908f5c8420331300055A63E6284F522", 22 | "liquidation": "0xf9773f2D869Bdbe0B6aC6D6fD7df82b82C998DC7", 23 | "governance": "0x78eE171d6c8d3808B72dAb8CE647719dB3bb4cC9", 24 | "exec": "0xF7B8611008Ed073Ef348FE130671688BBb20409d", 25 | "flashLoan": "0x0e60a8406a94787842f07221d2Fb5Bf19856CeA5", 26 | "swap": "0x86ea9f57d81Bf0C69Ff71114522fB3f29230DbA6", 27 | "eulStakes": "0x9b1D89ca454181Ba1fa6a498913C3BCf3529BBf0", 28 | "eulDistributor": "0xef6e4C95c3b7BaAF298D1d0B257030F7b30d2752" 29 | } 30 | -------------------------------------------------------------------------------- /contracts/Base.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | //import "hardhat/console.sol"; // DEV_MODE 5 | 6 | import "./Storage.sol"; 7 | import "./Events.sol"; 8 | import "./Proxy.sol"; 9 | 10 | abstract contract Base is Storage, Events { 11 | // Modules 12 | 13 | function _createProxy(uint proxyModuleId) internal returns (address) { 14 | require(proxyModuleId != 0, "e/create-proxy/invalid-module"); 15 | require(proxyModuleId <= MAX_EXTERNAL_MODULEID, "e/create-proxy/internal-module"); 16 | 17 | // If we've already created a proxy for a single-proxy module, just return it: 18 | 19 | if (proxyLookup[proxyModuleId] != address(0)) return proxyLookup[proxyModuleId]; 20 | 21 | // Otherwise create a proxy: 22 | 23 | address proxyAddr = address(new Proxy()); 24 | 25 | if (proxyModuleId <= MAX_EXTERNAL_SINGLE_PROXY_MODULEID) proxyLookup[proxyModuleId] = proxyAddr; 26 | 27 | trustedSenders[proxyAddr] = TrustedSenderInfo({ moduleId: uint32(proxyModuleId), moduleImpl: address(0) }); 28 | 29 | emit ProxyCreated(proxyAddr, proxyModuleId); 30 | 31 | return proxyAddr; 32 | } 33 | 34 | function callInternalModule(uint moduleId, bytes memory input) internal returns (bytes memory) { 35 | (bool success, bytes memory result) = moduleLookup[moduleId].delegatecall(input); 36 | if (!success) revertBytes(result); 37 | return result; 38 | } 39 | 40 | 41 | 42 | // Modifiers 43 | 44 | modifier nonReentrant() { 45 | require(reentrancyLock == REENTRANCYLOCK__UNLOCKED, "e/reentrancy"); 46 | 47 | reentrancyLock = REENTRANCYLOCK__LOCKED; 48 | _; 49 | reentrancyLock = REENTRANCYLOCK__UNLOCKED; 50 | } 51 | 52 | modifier reentrantOK() { // documentation only 53 | _; 54 | } 55 | 56 | // Used to flag functions which do not modify storage, but do perform a delegate call 57 | // to a view function, which prohibits a standard view modifier. The flag is used to 58 | // patch state mutability in compiled ABIs and interfaces. 59 | modifier staticDelegate() { 60 | _; 61 | } 62 | 63 | // WARNING: Must be very careful with this modifier. It resets the free memory pointer 64 | // to the value it was when the function started. This saves gas if more memory will 65 | // be allocated in the future. However, if the memory will be later referenced 66 | // (for example because the function has returned a pointer to it) then you cannot 67 | // use this modifier. 68 | 69 | modifier FREEMEM() { 70 | uint origFreeMemPtr; 71 | 72 | assembly { 73 | origFreeMemPtr := mload(0x40) 74 | } 75 | 76 | _; 77 | 78 | /* 79 | assembly { // DEV_MODE: overwrite the freed memory with garbage to detect bugs 80 | let garbage := 0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF 81 | for { let i := origFreeMemPtr } lt(i, mload(0x40)) { i := add(i, 32) } { mstore(i, garbage) } 82 | } 83 | */ 84 | 85 | assembly { 86 | mstore(0x40, origFreeMemPtr) 87 | } 88 | } 89 | 90 | 91 | 92 | // Error handling 93 | 94 | function revertBytes(bytes memory errMsg) internal pure { 95 | if (errMsg.length > 0) { 96 | assembly { 97 | revert(add(32, errMsg), mload(errMsg)) 98 | } 99 | } 100 | 101 | revert("e/empty-error"); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /contracts/BaseIRM.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "./BaseModule.sol"; 6 | 7 | abstract contract BaseIRM is BaseModule { 8 | constructor(uint moduleId_, bytes32 moduleGitCommit_) BaseModule(moduleId_, moduleGitCommit_) {} 9 | 10 | int96 internal constant MAX_ALLOWED_INTEREST_RATE = int96(int(uint(5 * 1e27) / SECONDS_PER_YEAR)); // 500% APR 11 | int96 internal constant MIN_ALLOWED_INTEREST_RATE = 0; 12 | 13 | function computeInterestRateImpl(address, uint32) internal virtual returns (int96); 14 | 15 | function computeInterestRate(address underlying, uint32 utilisation) external returns (int96) { 16 | int96 rate = computeInterestRateImpl(underlying, utilisation); 17 | 18 | if (rate > MAX_ALLOWED_INTEREST_RATE) rate = MAX_ALLOWED_INTEREST_RATE; 19 | else if (rate < MIN_ALLOWED_INTEREST_RATE) rate = MIN_ALLOWED_INTEREST_RATE; 20 | 21 | return rate; 22 | } 23 | 24 | function reset(address underlying, bytes calldata resetParams) external virtual {} 25 | } 26 | -------------------------------------------------------------------------------- /contracts/BaseIRMLinearKink.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "./BaseIRM.sol"; 6 | 7 | 8 | contract BaseIRMLinearKink is BaseIRM { 9 | uint public immutable baseRate; 10 | uint public immutable slope1; 11 | uint public immutable slope2; 12 | uint public immutable kink; 13 | 14 | constructor(uint moduleId_, bytes32 moduleGitCommit_, uint baseRate_, uint slope1_, uint slope2_, uint kink_) BaseIRM(moduleId_, moduleGitCommit_) { 15 | baseRate = baseRate_; 16 | slope1 = slope1_; 17 | slope2 = slope2_; 18 | kink = kink_; 19 | } 20 | 21 | function computeInterestRateImpl(address, uint32 utilisation) internal override view returns (int96) { 22 | uint ir = baseRate; 23 | 24 | if (utilisation <= kink) { 25 | ir += utilisation * slope1; 26 | } else { 27 | ir += kink * slope1; 28 | ir += slope2 * (utilisation - kink); 29 | } 30 | 31 | return int96(int(ir)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /contracts/BaseModule.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "./Base.sol"; 6 | 7 | 8 | abstract contract BaseModule is Base { 9 | // Construction 10 | 11 | // public accessors common to all modules 12 | 13 | uint immutable public moduleId; 14 | bytes32 immutable public moduleGitCommit; 15 | 16 | constructor(uint moduleId_, bytes32 moduleGitCommit_) { 17 | moduleId = moduleId_; 18 | moduleGitCommit = moduleGitCommit_; 19 | } 20 | 21 | 22 | // Accessing parameters 23 | 24 | function unpackTrailingParamMsgSender() internal pure returns (address msgSender) { 25 | assembly { 26 | msgSender := shr(96, calldataload(sub(calldatasize(), 40))) 27 | } 28 | } 29 | 30 | function unpackTrailingParams() internal pure returns (address msgSender, address proxyAddr) { 31 | assembly { 32 | msgSender := shr(96, calldataload(sub(calldatasize(), 40))) 33 | proxyAddr := shr(96, calldataload(sub(calldatasize(), 20))) 34 | } 35 | } 36 | 37 | 38 | // Emit logs via proxies 39 | 40 | function emitViaProxy_Transfer(address proxyAddr, address from, address to, uint value) internal FREEMEM { 41 | (bool success,) = proxyAddr.call(abi.encodePacked( 42 | uint8(3), 43 | keccak256(bytes('Transfer(address,address,uint256)')), 44 | bytes32(uint(uint160(from))), 45 | bytes32(uint(uint160(to))), 46 | value 47 | )); 48 | require(success, "e/log-proxy-fail"); 49 | } 50 | 51 | function emitViaProxy_Approval(address proxyAddr, address owner, address spender, uint value) internal FREEMEM { 52 | (bool success,) = proxyAddr.call(abi.encodePacked( 53 | uint8(3), 54 | keccak256(bytes('Approval(address,address,uint256)')), 55 | bytes32(uint(uint160(owner))), 56 | bytes32(uint(uint160(spender))), 57 | value 58 | )); 59 | require(success, "e/log-proxy-fail"); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /contracts/Constants.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | abstract contract Constants { 6 | // Universal 7 | 8 | uint internal constant SECONDS_PER_YEAR = 365.2425 * 86400; // Gregorian calendar 9 | 10 | 11 | // Protocol parameters 12 | 13 | uint internal constant MAX_SANE_AMOUNT = type(uint112).max; 14 | uint internal constant MAX_SANE_SMALL_AMOUNT = type(uint96).max; 15 | uint internal constant MAX_SANE_DEBT_AMOUNT = type(uint144).max; 16 | uint internal constant INTERNAL_DEBT_PRECISION = 1e9; 17 | uint internal constant MAX_ENTERED_MARKETS = 10; // per sub-account 18 | uint internal constant MAX_POSSIBLE_ENTERED_MARKETS = 2**32; // limited by size of AccountStorage.numMarketsEntered 19 | uint internal constant CONFIG_FACTOR_SCALE = 4_000_000_000; // must fit into a uint32 20 | uint internal constant RESERVE_FEE_SCALE = 4_000_000_000; // must fit into a uint32 21 | uint32 internal constant DEFAULT_RESERVE_FEE = uint32(0.23 * 4_000_000_000); 22 | uint internal constant INITIAL_RESERVES = 1e6; 23 | uint internal constant INITIAL_INTEREST_ACCUMULATOR = 1e27; 24 | uint internal constant AVERAGE_LIQUIDITY_PERIOD = 24 * 60 * 60; 25 | uint16 internal constant MIN_UNISWAP3_OBSERVATION_CARDINALITY = 144; 26 | uint24 internal constant DEFAULT_TWAP_WINDOW_SECONDS = 30 * 60; 27 | uint32 internal constant DEFAULT_BORROW_FACTOR = uint32(0.28 * 4_000_000_000); 28 | uint32 internal constant SELF_COLLATERAL_FACTOR = uint32(0.95 * 4_000_000_000); 29 | 30 | 31 | // Implementation internals 32 | 33 | uint internal constant REENTRANCYLOCK__UNLOCKED = 1; 34 | uint internal constant REENTRANCYLOCK__LOCKED = 2; 35 | 36 | uint8 internal constant DEFERLIQUIDITY__NONE = 0; 37 | uint8 internal constant DEFERLIQUIDITY__CLEAN = 1; 38 | uint8 internal constant DEFERLIQUIDITY__DIRTY = 2; 39 | 40 | 41 | // Pricing types 42 | 43 | uint16 internal constant PRICINGTYPE__PEGGED = 1; 44 | uint16 internal constant PRICINGTYPE__UNISWAP3_TWAP = 2; 45 | uint16 internal constant PRICINGTYPE__FORWARDED = 3; 46 | uint16 internal constant PRICINGTYPE__CHAINLINK = 4; 47 | 48 | // Correct pricing types are always less than this value 49 | uint16 internal constant PRICINGTYPE__OUT_OF_BOUNDS = 5; 50 | 51 | 52 | // Modules 53 | 54 | // Public single-proxy modules 55 | uint internal constant MODULEID__INSTALLER = 1; 56 | uint internal constant MODULEID__MARKETS = 2; 57 | uint internal constant MODULEID__LIQUIDATION = 3; 58 | uint internal constant MODULEID__GOVERNANCE = 4; 59 | uint internal constant MODULEID__EXEC = 5; 60 | uint internal constant MODULEID__SWAP = 6; 61 | uint internal constant MODULEID__SWAPHUB = 7; 62 | 63 | uint internal constant MAX_EXTERNAL_SINGLE_PROXY_MODULEID = 499_999; 64 | 65 | // Public multi-proxy modules 66 | uint internal constant MODULEID__ETOKEN = 500_000; 67 | uint internal constant MODULEID__DTOKEN = 500_001; 68 | 69 | uint internal constant MAX_EXTERNAL_MODULEID = 999_999; 70 | 71 | // Internal modules 72 | uint internal constant MODULEID__RISK_MANAGER = 1_000_000; 73 | 74 | // Interest rate models 75 | // Default for new markets 76 | uint internal constant MODULEID__IRM_DEFAULT = 2_000_000; 77 | // Testing-only 78 | uint internal constant MODULEID__IRM_ZERO = 2_000_001; 79 | uint internal constant MODULEID__IRM_FIXED = 2_000_002; 80 | uint internal constant MODULEID__IRM_LINEAR = 2_000_100; 81 | // Classes 82 | uint internal constant MODULEID__IRM_CLASS__STABLE = 2_000_500; 83 | uint internal constant MODULEID__IRM_CLASS__MAJOR = 2_000_501; 84 | uint internal constant MODULEID__IRM_CLASS__MIDCAP = 2_000_502; 85 | uint internal constant MODULEID__IRM_CLASS__MEGA = 2_000_503; 86 | uint internal constant MODULEID__IRM_CLASS__LIDO = 2_000_504; 87 | uint internal constant MODULEID__IRM_CLASS__USDT = 2_000_505; 88 | uint internal constant MODULEID__IRM_CLASS__OHM = 2_000_506; 89 | 90 | // Swap types 91 | uint internal constant SWAP_TYPE__UNI_EXACT_INPUT_SINGLE = 1; 92 | uint internal constant SWAP_TYPE__UNI_EXACT_INPUT = 2; 93 | uint internal constant SWAP_TYPE__UNI_EXACT_OUTPUT_SINGLE = 3; 94 | uint internal constant SWAP_TYPE__UNI_EXACT_OUTPUT = 4; 95 | uint internal constant SWAP_TYPE__1INCH = 5; 96 | 97 | uint internal constant SWAP_TYPE__UNI_EXACT_OUTPUT_SINGLE_REPAY = 6; 98 | uint internal constant SWAP_TYPE__UNI_EXACT_OUTPUT_REPAY = 7; 99 | } 100 | -------------------------------------------------------------------------------- /contracts/Euler.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "./Base.sol"; 6 | 7 | 8 | /// @notice Main storage contract for the Euler system 9 | contract Euler is Base { 10 | constructor(address admin, address installerModule) { 11 | emit Genesis(); 12 | 13 | reentrancyLock = REENTRANCYLOCK__UNLOCKED; 14 | upgradeAdmin = admin; 15 | governorAdmin = admin; 16 | 17 | moduleLookup[MODULEID__INSTALLER] = installerModule; 18 | address installerProxy = _createProxy(MODULEID__INSTALLER); 19 | trustedSenders[installerProxy].moduleImpl = installerModule; 20 | } 21 | 22 | string public constant name = "Euler Protocol"; 23 | 24 | /// @notice Lookup the current implementation contract for a module 25 | /// @param moduleId Fixed constant that refers to a module type (ie MODULEID__ETOKEN) 26 | /// @return An internal address specifies the module's implementation code 27 | function moduleIdToImplementation(uint moduleId) external view returns (address) { 28 | return moduleLookup[moduleId]; 29 | } 30 | 31 | /// @notice Lookup a proxy that can be used to interact with a module (only valid for single-proxy modules) 32 | /// @param moduleId Fixed constant that refers to a module type (ie MODULEID__MARKETS) 33 | /// @return An address that should be cast to the appropriate module interface, ie IEulerMarkets(moduleIdToProxy(2)) 34 | function moduleIdToProxy(uint moduleId) external view returns (address) { 35 | return proxyLookup[moduleId]; 36 | } 37 | 38 | function dispatch() external reentrantOK { 39 | uint32 moduleId = trustedSenders[msg.sender].moduleId; 40 | address moduleImpl = trustedSenders[msg.sender].moduleImpl; 41 | 42 | require(moduleId != 0, "e/sender-not-trusted"); 43 | 44 | if (moduleImpl == address(0)) moduleImpl = moduleLookup[moduleId]; 45 | 46 | uint msgDataLength = msg.data.length; 47 | require(msgDataLength >= (4 + 4 + 20), "e/input-too-short"); 48 | 49 | assembly { 50 | let payloadSize := sub(calldatasize(), 4) 51 | calldatacopy(0, 4, payloadSize) 52 | mstore(payloadSize, shl(96, caller())) 53 | 54 | let result := delegatecall(gas(), moduleImpl, 0, add(payloadSize, 20), 0, 0) 55 | 56 | returndatacopy(0, 0, returndatasize()) 57 | 58 | switch result 59 | case 0 { revert(0, returndatasize()) } 60 | default { return(0, returndatasize()) } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /contracts/Events.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "./Storage.sol"; 6 | 7 | abstract contract Events { 8 | event Genesis(); 9 | 10 | 11 | event ProxyCreated(address indexed proxy, uint moduleId); 12 | event MarketActivated(address indexed underlying, address indexed eToken, address indexed dToken); 13 | event PTokenActivated(address indexed underlying, address indexed pToken); 14 | 15 | event EnterMarket(address indexed underlying, address indexed account); 16 | event ExitMarket(address indexed underlying, address indexed account); 17 | 18 | event Deposit(address indexed underlying, address indexed account, uint amount); 19 | event Withdraw(address indexed underlying, address indexed account, uint amount); 20 | event Borrow(address indexed underlying, address indexed account, uint amount); 21 | event Repay(address indexed underlying, address indexed account, uint amount); 22 | 23 | event Liquidation(address indexed liquidator, address indexed violator, address indexed underlying, address collateral, uint repay, uint yield, uint healthScore, uint baseDiscount, uint discount); 24 | 25 | event TrackAverageLiquidity(address indexed account); 26 | event UnTrackAverageLiquidity(address indexed account); 27 | event DelegateAverageLiquidity(address indexed account, address indexed delegate); 28 | 29 | event PTokenWrap(address indexed underlying, address indexed account, uint amount); 30 | event PTokenUnWrap(address indexed underlying, address indexed account, uint amount); 31 | 32 | event AssetStatus(address indexed underlying, uint totalBalances, uint totalBorrows, uint96 reserveBalance, uint poolSize, uint interestAccumulator, int96 interestRate, uint timestamp); 33 | 34 | 35 | event RequestDeposit(address indexed account, uint amount); 36 | event RequestWithdraw(address indexed account, uint amount); 37 | event RequestMint(address indexed account, uint amount); 38 | event RequestBurn(address indexed account, uint amount); 39 | event RequestTransferEToken(address indexed from, address indexed to, uint amount); 40 | event RequestDonate(address indexed account, uint amount); 41 | 42 | event RequestBorrow(address indexed account, uint amount); 43 | event RequestRepay(address indexed account, uint amount); 44 | event RequestTransferDToken(address indexed from, address indexed to, uint amount); 45 | 46 | event RequestLiquidate(address indexed liquidator, address indexed violator, address indexed underlying, address collateral, uint repay, uint minYield); 47 | 48 | 49 | event InstallerSetUpgradeAdmin(address indexed newUpgradeAdmin); 50 | event InstallerSetGovernorAdmin(address indexed newGovernorAdmin); 51 | event InstallerInstallModule(uint indexed moduleId, address indexed moduleImpl, bytes32 moduleGitCommit); 52 | 53 | 54 | event GovSetAssetConfig(address indexed underlying, Storage.AssetConfig newConfig); 55 | event GovSetIRM(address indexed underlying, uint interestRateModel, bytes resetParams); 56 | event GovSetPricingConfig(address indexed underlying, uint16 newPricingType, uint32 newPricingParameter); 57 | event GovSetReserveFee(address indexed underlying, uint32 newReserveFee); 58 | event GovConvertReserves(address indexed underlying, address indexed recipient, uint amount); 59 | event GovSetChainlinkPriceFeed(address indexed underlying, address chainlinkAggregator); 60 | 61 | event RequestSwap(address indexed accountIn, address indexed accountOut, address indexed underlyingIn, address underlyingOut, uint amount, uint swapType); 62 | event RequestSwapHub(address indexed accountIn, address indexed accountOut, address indexed underlyingIn, address underlyingOut, uint amountIn, uint amountOut, uint mode, address swapHandler); 63 | event RequestSwapHubRepay(address indexed accountIn, address indexed accountOut, address indexed underlyingIn, address underlyingOut, uint targetDebt, address swapHandler); 64 | } 65 | -------------------------------------------------------------------------------- /contracts/IRiskManager.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "./Storage.sol"; 6 | 7 | // This interface is used to avoid a circular dependency between BaseLogic and RiskManager 8 | 9 | interface IRiskManager { 10 | struct NewMarketParameters { 11 | uint16 pricingType; 12 | uint32 pricingParameters; 13 | 14 | Storage.AssetConfig config; 15 | } 16 | 17 | struct LiquidityStatus { 18 | uint collateralValue; 19 | uint liabilityValue; 20 | uint numBorrows; 21 | bool borrowIsolated; 22 | } 23 | 24 | struct AssetLiquidity { 25 | address underlying; 26 | LiquidityStatus status; 27 | } 28 | 29 | function getNewMarketParameters(address underlying) external returns (NewMarketParameters memory); 30 | 31 | function requireLiquidity(address account) external view; 32 | function computeLiquidity(address account) external view returns (LiquidityStatus memory status); 33 | function computeAssetLiquidities(address account) external view returns (AssetLiquidity[] memory assets); 34 | 35 | function getPrice(address underlying) external view returns (uint twap, uint twapPeriod); 36 | function getPriceFull(address underlying) external view returns (uint twap, uint twapPeriod, uint currPrice); 37 | } 38 | -------------------------------------------------------------------------------- /contracts/Interfaces.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | 6 | interface IERC20 { 7 | event Approval(address indexed owner, address indexed spender, uint value); 8 | event Transfer(address indexed from, address indexed to, uint value); 9 | 10 | function name() external view returns (string memory); 11 | function symbol() external view returns (string memory); 12 | function decimals() external view returns (uint8); 13 | function totalSupply() external view returns (uint); 14 | function balanceOf(address owner) external view returns (uint); 15 | function allowance(address owner, address spender) external view returns (uint); 16 | 17 | function approve(address spender, uint value) external returns (bool); 18 | function transfer(address to, uint value) external returns (bool); 19 | function transferFrom(address from, address to, uint value) external returns (bool); 20 | } 21 | 22 | interface IERC20Permit { 23 | function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external; 24 | function permit(address holder, address spender, uint256 nonce, uint256 expiry, bool allowed, uint8 v, bytes32 r, bytes32 s) external; 25 | function permit(address owner, address spender, uint value, uint deadline, bytes calldata signature) external; 26 | } 27 | 28 | interface IERC3156FlashBorrower { 29 | function onFlashLoan(address initiator, address token, uint256 amount, uint256 fee, bytes calldata data) external returns (bytes32); 30 | } 31 | 32 | interface IERC3156FlashLender { 33 | function maxFlashLoan(address token) external view returns (uint256); 34 | function flashFee(address token, uint256 amount) external view returns (uint256); 35 | function flashLoan(IERC3156FlashBorrower receiver, address token, uint256 amount, bytes calldata data) external returns (bool); 36 | } 37 | -------------------------------------------------------------------------------- /contracts/Proxy.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | contract Proxy { 6 | address immutable creator; 7 | 8 | constructor() { 9 | creator = msg.sender; 10 | } 11 | 12 | // External interface 13 | 14 | fallback() external { 15 | address creator_ = creator; 16 | 17 | if (msg.sender == creator_) { 18 | assembly { 19 | mstore(0, 0) 20 | calldatacopy(31, 0, calldatasize()) 21 | 22 | switch mload(0) // numTopics 23 | case 0 { log0(32, sub(calldatasize(), 1)) } 24 | case 1 { log1(64, sub(calldatasize(), 33), mload(32)) } 25 | case 2 { log2(96, sub(calldatasize(), 65), mload(32), mload(64)) } 26 | case 3 { log3(128, sub(calldatasize(), 97), mload(32), mload(64), mload(96)) } 27 | case 4 { log4(160, sub(calldatasize(), 129), mload(32), mload(64), mload(96), mload(128)) } 28 | default { revert(0, 0) } 29 | 30 | return(0, 0) 31 | } 32 | } else { 33 | assembly { 34 | mstore(0, 0xe9c4a3ac00000000000000000000000000000000000000000000000000000000) // dispatch() selector 35 | calldatacopy(4, 0, calldatasize()) 36 | mstore(add(4, calldatasize()), shl(96, caller())) 37 | 38 | let result := call(gas(), creator_, 0, 0, add(24, calldatasize()), 0, 0) 39 | returndatacopy(0, 0, returndatasize()) 40 | 41 | switch result 42 | case 0 { revert(0, returndatasize()) } 43 | default { return(0, returndatasize()) } 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /contracts/Storage.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "./Constants.sol"; 6 | 7 | abstract contract Storage is Constants { 8 | // Dispatcher and upgrades 9 | 10 | uint internal reentrancyLock; 11 | 12 | address upgradeAdmin; 13 | address governorAdmin; 14 | 15 | mapping(uint => address) moduleLookup; // moduleId => module implementation 16 | mapping(uint => address) proxyLookup; // moduleId => proxy address (only for single-proxy modules) 17 | 18 | struct TrustedSenderInfo { 19 | uint32 moduleId; // 0 = un-trusted 20 | address moduleImpl; // only non-zero for external single-proxy modules 21 | } 22 | 23 | mapping(address => TrustedSenderInfo) trustedSenders; // sender address => moduleId (0 = un-trusted) 24 | 25 | 26 | 27 | // Account-level state 28 | // Sub-accounts are considered distinct accounts 29 | 30 | struct AccountStorage { 31 | // Packed slot: 1 + 5 + 4 + 20 = 30 32 | uint8 deferLiquidityStatus; 33 | uint40 lastAverageLiquidityUpdate; 34 | uint32 numMarketsEntered; 35 | address firstMarketEntered; 36 | 37 | uint averageLiquidity; 38 | address averageLiquidityDelegate; 39 | } 40 | 41 | mapping(address => AccountStorage) accountLookup; 42 | mapping(address => address[MAX_POSSIBLE_ENTERED_MARKETS]) marketsEntered; 43 | 44 | 45 | 46 | // Markets and assets 47 | 48 | struct AssetConfig { 49 | // Packed slot: 20 + 1 + 4 + 4 + 3 = 32 50 | address eTokenAddress; 51 | bool borrowIsolated; 52 | uint32 collateralFactor; 53 | uint32 borrowFactor; 54 | uint24 twapWindow; 55 | } 56 | 57 | struct UserAsset { 58 | uint112 balance; 59 | uint144 owed; 60 | 61 | uint interestAccumulator; 62 | } 63 | 64 | struct AssetStorage { 65 | // Packed slot: 5 + 1 + 4 + 12 + 4 + 2 + 4 = 32 66 | uint40 lastInterestAccumulatorUpdate; 67 | uint8 underlyingDecimals; // Not dynamic, but put here to live in same storage slot 68 | uint32 interestRateModel; 69 | int96 interestRate; 70 | uint32 reserveFee; 71 | uint16 pricingType; 72 | uint32 pricingParameters; 73 | 74 | address underlying; 75 | uint96 reserveBalance; 76 | 77 | address dTokenAddress; 78 | 79 | uint112 totalBalances; 80 | uint144 totalBorrows; 81 | 82 | uint interestAccumulator; 83 | 84 | mapping(address => UserAsset) users; 85 | 86 | mapping(address => mapping(address => uint)) eTokenAllowance; 87 | mapping(address => mapping(address => uint)) dTokenAllowance; 88 | } 89 | 90 | mapping(address => AssetConfig) internal underlyingLookup; // underlying => AssetConfig 91 | mapping(address => AssetStorage) internal eTokenLookup; // EToken => AssetStorage 92 | mapping(address => address) internal dTokenLookup; // DToken => EToken 93 | mapping(address => address) internal pTokenLookup; // PToken => underlying 94 | mapping(address => address) internal reversePTokenLookup; // underlying => PToken 95 | mapping(address => address) internal chainlinkPriceFeedLookup; // underlying => chainlinkAggregator 96 | } 97 | -------------------------------------------------------------------------------- /contracts/Utils.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "./Interfaces.sol"; 6 | 7 | library Utils { 8 | function safeTransferFrom(address token, address from, address to, uint value) internal { 9 | (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transferFrom.selector, from, to, value)); 10 | require(success && (data.length == 0 || abi.decode(data, (bool))), string(data)); 11 | } 12 | 13 | function safeTransfer(address token, address to, uint value) internal { 14 | (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transfer.selector, to, value)); 15 | require(success && (data.length == 0 || abi.decode(data, (bool))), string(data)); 16 | } 17 | 18 | function safeApprove(address token, address to, uint value) internal { 19 | (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.approve.selector, to, value)); 20 | require(success && (data.length == 0 || abi.decode(data, (bool))), string(data)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /contracts/adaptors/FlashLoan.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | import "../modules/Exec.sol"; 5 | import "../modules/Markets.sol"; 6 | import "../modules/DToken.sol"; 7 | import "../Interfaces.sol"; 8 | import "../Utils.sol"; 9 | 10 | contract FlashLoan is IERC3156FlashLender, IDeferredLiquidityCheck { 11 | bytes32 public constant CALLBACK_SUCCESS = keccak256("ERC3156FlashBorrower.onFlashLoan"); 12 | 13 | address immutable eulerAddress; 14 | Exec immutable exec; 15 | Markets immutable markets; 16 | 17 | bool internal _isDeferredLiquidityCheck; 18 | 19 | constructor(address euler_, address exec_, address markets_) { 20 | eulerAddress = euler_; 21 | exec = Exec(exec_); 22 | markets = Markets(markets_); 23 | } 24 | 25 | function maxFlashLoan(address token) override external view returns (uint) { 26 | address eTokenAddress = markets.underlyingToEToken(token); 27 | 28 | return eTokenAddress == address(0) ? 0 : IERC20(token).balanceOf(eulerAddress); 29 | } 30 | 31 | function flashFee(address token, uint) override external view returns (uint) { 32 | require(markets.underlyingToEToken(token) != address(0), "e/flash-loan/unsupported-token"); 33 | 34 | return 0; 35 | } 36 | 37 | function flashLoan(IERC3156FlashBorrower receiver, address token, uint256 amount, bytes calldata data) override external returns (bool) { 38 | require(markets.underlyingToEToken(token) != address(0), "e/flash-loan/unsupported-token"); 39 | 40 | if(!_isDeferredLiquidityCheck) { 41 | exec.deferLiquidityCheck(address(this), abi.encode(receiver, token, amount, data, msg.sender)); 42 | _isDeferredLiquidityCheck = false; 43 | } else { 44 | _loan(receiver, token, amount, data, msg.sender); 45 | } 46 | 47 | return true; 48 | } 49 | 50 | function onDeferredLiquidityCheck(bytes memory encodedData) override external { 51 | require(msg.sender == eulerAddress, "e/flash-loan/on-deferred-caller"); 52 | (IERC3156FlashBorrower receiver, address token, uint amount, bytes memory data, address msgSender) = 53 | abi.decode(encodedData, (IERC3156FlashBorrower, address, uint, bytes, address)); 54 | 55 | _isDeferredLiquidityCheck = true; 56 | _loan(receiver, token, amount, data, msgSender); 57 | 58 | _exitAllMarkets(); 59 | } 60 | 61 | function _loan(IERC3156FlashBorrower receiver, address token, uint256 amount, bytes memory data, address msgSender) internal { 62 | DToken dToken = DToken(markets.underlyingToDToken(token)); 63 | 64 | dToken.borrow(0, amount); 65 | Utils.safeTransfer(token, address(receiver), amount); 66 | 67 | require( 68 | receiver.onFlashLoan(msgSender, token, amount, 0, data) == CALLBACK_SUCCESS, 69 | "e/flash-loan/callback" 70 | ); 71 | 72 | Utils.safeTransferFrom(token, address(receiver), address(this), amount); 73 | require(IERC20(token).balanceOf(address(this)) >= amount, 'e/flash-loan/pull-amount'); 74 | 75 | uint allowance = IERC20(token).allowance(address(this), eulerAddress); 76 | if(allowance < amount) { 77 | (bool success,) = token.call(abi.encodeWithSelector(IERC20(token).approve.selector, eulerAddress, type(uint).max)); 78 | require(success, "e/flash-loan/approve"); 79 | } 80 | 81 | dToken.repay(0, amount); 82 | } 83 | 84 | function _exitAllMarkets() internal { 85 | address[] memory enteredMarkets = markets.getEnteredMarkets(address(this)); 86 | 87 | for (uint i = 0; i < enteredMarkets.length; ++i) { 88 | markets.exitMarket(0, enteredMarkets[i]); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /contracts/mining/EulDistributor.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../vendor/MerkleProof.sol"; 6 | import "../Utils.sol"; 7 | 8 | interface IEulStakes { 9 | function stakeGift(address beneficiary, address underlying, uint amount) external; 10 | } 11 | 12 | contract EulDistributor { 13 | address public immutable eul; 14 | address public immutable eulStakes; 15 | string public constant name = "EUL Distributor"; 16 | 17 | address public owner; 18 | bytes32 public currRoot; 19 | bytes32 public prevRoot; 20 | mapping(address => mapping(address => uint)) public claimed; // account -> token -> amount 21 | 22 | event OwnerChanged(address indexed newOwner); 23 | 24 | constructor(address eul_, address eulStakes_) { 25 | eul = eul_; 26 | eulStakes = eulStakes_; 27 | owner = msg.sender; 28 | Utils.safeApprove(eul_, eulStakes_, type(uint).max); 29 | } 30 | 31 | // Owner functions 32 | 33 | modifier onlyOwner { 34 | require(msg.sender == owner, "unauthorized"); 35 | _; 36 | } 37 | 38 | function transferOwnership(address newOwner) external onlyOwner { 39 | owner = newOwner; 40 | emit OwnerChanged(newOwner); 41 | } 42 | 43 | function updateRoot(bytes32 newRoot) external onlyOwner { 44 | prevRoot = currRoot; 45 | currRoot = newRoot; 46 | } 47 | 48 | // Claiming 49 | 50 | /// @notice Claim distributed tokens 51 | /// @param account Address that should receive tokens 52 | /// @param token Address of token being claimed (ie EUL) 53 | /// @param proof Merkle proof that validates this claim 54 | /// @param stake If non-zero, then the address of a token to auto-stake to, instead of claiming 55 | function claim(address account, address token, uint claimable, bytes32[] calldata proof, address stake) external { 56 | bytes32 candidateRoot = MerkleProof.processProof(proof, keccak256(abi.encodePacked(account, token, claimable))); // 72 byte leaf 57 | require(candidateRoot == currRoot || candidateRoot == prevRoot, "proof invalid/expired"); 58 | 59 | uint alreadyClaimed = claimed[account][token]; 60 | require(claimable > alreadyClaimed, "already claimed"); 61 | 62 | uint amount; 63 | unchecked { 64 | amount = claimable - alreadyClaimed; 65 | } 66 | 67 | claimed[account][token] = claimable; 68 | 69 | if (stake == address(0)) { 70 | Utils.safeTransfer(token, account, amount); 71 | } else { 72 | require(msg.sender == account, "can only auto-stake for yourself"); 73 | require(token == eul, "can only auto-stake EUL"); 74 | IEulStakes(eulStakes).stakeGift(account, stake, amount); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /contracts/mining/EulDistributorOwner.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | interface IEulDistributor { 6 | function updateRoot(bytes32 newRoot) external; 7 | } 8 | 9 | contract EulDistributorOwner { 10 | string public constant name = "EUL Distributor Owner"; 11 | 12 | address public immutable eulDistributor; 13 | address public owner; 14 | address public updater; 15 | 16 | constructor(address eulDistributor_, address owner_, address updater_) { 17 | eulDistributor = eulDistributor_; 18 | owner = owner_; 19 | updater = updater_; 20 | } 21 | 22 | // Owner-only functions 23 | 24 | modifier onlyOwner { 25 | require(msg.sender == owner, "unauthorized"); 26 | _; 27 | } 28 | 29 | function changeOwner(address newOwner) external onlyOwner { 30 | owner = newOwner; 31 | } 32 | 33 | function changeUpdater(address newUpdater) external onlyOwner { 34 | updater = newUpdater; 35 | } 36 | 37 | function execute(address destination, uint value, bytes calldata payload) external onlyOwner { 38 | (bool success,) = destination.call{value: value}(payload); 39 | require(success, "execute failure"); 40 | } 41 | 42 | // Updater-only functions 43 | 44 | function updateRoot(bytes32 newRoot) external { 45 | require(msg.sender == updater, "unauthorized"); 46 | IEulDistributor(eulDistributor).updateRoot(newRoot); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /contracts/mining/EulStakes.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../Utils.sol"; 6 | 7 | contract EulStakes { 8 | address public immutable eul; 9 | string public constant name = "EUL Stakes"; 10 | mapping(address => mapping(address => uint)) userStaked; 11 | 12 | event Stake(address indexed who, address indexed underlying, address sender, uint newAmount); 13 | 14 | constructor(address eul_) { 15 | eul = eul_; 16 | } 17 | 18 | /// @notice Retrieve current amount staked 19 | /// @param account User address 20 | /// @param underlying Token staked upon 21 | /// @return Amount of EUL token staked 22 | function staked(address account, address underlying) external view returns (uint) { 23 | return userStaked[account][underlying]; 24 | } 25 | 26 | /// @notice Staking operation item. Positive amount means to increase stake on this underlying, negative to decrease. 27 | struct StakeOp { 28 | address underlying; 29 | int amount; 30 | } 31 | 32 | /// @notice Modify stake of a series of underlyings. If the sum of all amounts is positive, then this amount of EUL will be transferred in from the sender's wallet. If negative, EUL will be transferred out to the sender's wallet. 33 | /// @param ops Array of operations to perform 34 | function stake(StakeOp[] memory ops) public { 35 | int delta = 0; 36 | 37 | for (uint i = 0; i < ops.length; ++i) { 38 | StakeOp memory op = ops[i]; 39 | if (op.amount == 0) continue; 40 | 41 | require(op.amount > -1e36 && op.amount < 1e36, "amount out of range"); 42 | 43 | uint newAmount; 44 | 45 | { 46 | int newAmountSigned = int(userStaked[msg.sender][op.underlying]) + op.amount; 47 | require(newAmountSigned >= 0, "insufficient staked"); 48 | newAmount = uint(newAmountSigned); 49 | } 50 | 51 | userStaked[msg.sender][op.underlying] = newAmount; 52 | emit Stake(msg.sender, op.underlying, msg.sender, newAmount); 53 | 54 | delta += op.amount; 55 | } 56 | 57 | if (delta > 0) { 58 | Utils.safeTransferFrom(eul, msg.sender, address(this), uint(delta)); 59 | } else if (delta < 0) { 60 | Utils.safeTransfer(eul, msg.sender, uint(-delta)); 61 | } 62 | } 63 | 64 | /// @notice Increase stake on an underlying, and transfer this stake to a beneficiary 65 | /// @param beneficiary Who is given credit for this staked EUL 66 | /// @param underlying The underlying token to be staked upon 67 | /// @param amount How much EUL to stake 68 | function stakeGift(address beneficiary, address underlying, uint amount) external { 69 | require(amount < 1e36, "amount out of range"); 70 | if (amount == 0) return; 71 | 72 | uint newAmount = userStaked[beneficiary][underlying] + amount; 73 | 74 | userStaked[beneficiary][underlying] = newAmount; 75 | emit Stake(beneficiary, underlying, msg.sender, newAmount); 76 | 77 | Utils.safeTransferFrom(eul, msg.sender, address(this), amount); 78 | } 79 | 80 | /// @notice Applies a permit() signature to EUL and then applies a sequence of staking operations 81 | /// @param ops Array of operations to perform 82 | /// @param value The value field of the permit message 83 | /// @param deadline The deadline field of the permit message 84 | /// @param v Signature field 85 | /// @param r Signature field 86 | /// @param s Signature field 87 | function stakePermit(StakeOp[] memory ops, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external { 88 | IERC20Permit(eul).permit(msg.sender, address(this), value, deadline, v, r, s); 89 | 90 | stake(ops); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /contracts/modules/Installer.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../BaseModule.sol"; 6 | 7 | 8 | contract Installer is BaseModule { 9 | constructor(bytes32 moduleGitCommit_) BaseModule(MODULEID__INSTALLER, moduleGitCommit_) {} 10 | 11 | modifier adminOnly { 12 | address msgSender = unpackTrailingParamMsgSender(); 13 | require(msgSender == upgradeAdmin, "e/installer/unauthorized"); 14 | _; 15 | } 16 | 17 | function getUpgradeAdmin() external view returns (address) { 18 | return upgradeAdmin; 19 | } 20 | 21 | function setUpgradeAdmin(address newUpgradeAdmin) external nonReentrant adminOnly { 22 | require(newUpgradeAdmin != address(0), "e/installer/bad-admin-addr"); 23 | upgradeAdmin = newUpgradeAdmin; 24 | emit InstallerSetUpgradeAdmin(newUpgradeAdmin); 25 | } 26 | 27 | function setGovernorAdmin(address newGovernorAdmin) external nonReentrant adminOnly { 28 | require(newGovernorAdmin != address(0), "e/installer/bad-gov-addr"); 29 | governorAdmin = newGovernorAdmin; 30 | emit InstallerSetGovernorAdmin(newGovernorAdmin); 31 | } 32 | 33 | function installModules(address[] memory moduleAddrs) external nonReentrant adminOnly { 34 | for (uint i = 0; i < moduleAddrs.length; ++i) { 35 | address moduleAddr = moduleAddrs[i]; 36 | uint newModuleId = BaseModule(moduleAddr).moduleId(); 37 | bytes32 moduleGitCommit = BaseModule(moduleAddr).moduleGitCommit(); 38 | 39 | moduleLookup[newModuleId] = moduleAddr; 40 | 41 | if (newModuleId <= MAX_EXTERNAL_SINGLE_PROXY_MODULEID) { 42 | address proxyAddr = _createProxy(newModuleId); 43 | trustedSenders[proxyAddr].moduleImpl = moduleAddr; 44 | } 45 | 46 | emit InstallerInstallModule(newModuleId, moduleAddr, moduleGitCommit); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /contracts/modules/interest-rate-models/IRMClassLido.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../BaseIRM.sol"; 6 | 7 | interface ILidoOracle { 8 | function getLastCompletedReportDelta() external view returns (uint postTotalPooledEther, uint preTotalPooledEther, uint timeElapsed); 9 | } 10 | 11 | interface IStETH { 12 | function getFee() external view returns (uint16 feeBasisPoints); 13 | } 14 | 15 | contract IRMClassLido is BaseIRM { 16 | uint constant SECONDS_PER_DAY = 24 * 60 * 60; 17 | uint constant MAX_ALLOWED_LIDO_INTEREST_RATE = 1e27 / SECONDS_PER_YEAR; // 100% APR 18 | uint constant LIDO_BASIS_POINT = 10000; 19 | address public immutable lidoOracle; 20 | address public immutable stETH; 21 | uint public immutable slope1; 22 | uint public immutable slope2; 23 | uint public immutable kink; 24 | 25 | struct IRMLidoStorage { 26 | int96 baseRate; 27 | uint64 lastCalled; 28 | } 29 | 30 | constructor(bytes32 moduleGitCommit_) BaseIRM(MODULEID__IRM_CLASS__LIDO, moduleGitCommit_) { 31 | lidoOracle = 0x442af784A788A5bd6F42A01Ebe9F287a871243fb; 32 | stETH = 0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84; 33 | 34 | // Base=Lido APY, Kink(80%)=8% APY Max=200% APY 35 | slope1 = 709783723; 36 | slope2 = 37689273223; 37 | kink = 3435973836; 38 | } 39 | 40 | function computeInterestRateImpl(address, uint32 utilisation) internal override returns (int96) { 41 | uint ir = 0; 42 | if (utilisation > 0) { 43 | IRMLidoStorage storage irmLido; 44 | { 45 | bytes32 storagePosition = keccak256("euler.irm.class.lido"); 46 | assembly { irmLido.slot := storagePosition } 47 | } 48 | 49 | if (block.timestamp - irmLido.lastCalled > SECONDS_PER_DAY) { 50 | (bool successReport, bytes memory dataReport) = lidoOracle.staticcall(abi.encodeWithSelector(ILidoOracle.getLastCompletedReportDelta.selector)); 51 | (bool successFee, bytes memory dataFee) = stETH.staticcall(abi.encodeWithSelector(IStETH.getFee.selector)); 52 | 53 | // if the external contract calls unsuccessful, the base rate will be set to the last stored value 54 | if (successReport && successFee && dataReport.length >= (3 * 32) && dataFee.length >= 32) { 55 | (uint postTotalPooledEther, uint preTotalPooledEther, uint timeElapsed) = abi.decode(dataReport, (uint, uint, uint)); 56 | uint16 lidoFee = abi.decode(dataFee, (uint16)); 57 | 58 | // do not support negative rebases 59 | // assure Lido reward fee is not greater than LIDO_BASIS_POINT 60 | uint baseRate = 0; 61 | if ( 62 | preTotalPooledEther != 0 && 63 | timeElapsed != 0 && 64 | preTotalPooledEther < postTotalPooledEther && 65 | lidoFee < LIDO_BASIS_POINT 66 | ) { 67 | unchecked { 68 | baseRate = 1e27 * (postTotalPooledEther - preTotalPooledEther) / (preTotalPooledEther * timeElapsed); 69 | 70 | // reflect Lido reward fee 71 | baseRate = baseRate * (LIDO_BASIS_POINT - lidoFee) / LIDO_BASIS_POINT; 72 | } 73 | } 74 | 75 | // update the storage only if the Lido oracle call was successful 76 | irmLido.baseRate = int96(int(baseRate)); 77 | irmLido.lastCalled = uint64(block.timestamp); 78 | } 79 | } 80 | 81 | ir = uint(int(irmLido.baseRate)); 82 | 83 | // avoids potential overflow in subsequent calculations 84 | if (ir > MAX_ALLOWED_LIDO_INTEREST_RATE) { 85 | ir = MAX_ALLOWED_LIDO_INTEREST_RATE; 86 | } 87 | } 88 | 89 | if (utilisation <= kink) { 90 | ir += utilisation * slope1; 91 | } else { 92 | ir += kink * slope1; 93 | ir += slope2 * (utilisation - kink); 94 | } 95 | 96 | return int96(int(ir)); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /contracts/modules/interest-rate-models/IRMClassMajor.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../BaseIRMLinearKink.sol"; 6 | 7 | 8 | contract IRMClassMajor is BaseIRMLinearKink { 9 | constructor(bytes32 moduleGitCommit_) 10 | BaseIRMLinearKink(MODULEID__IRM_CLASS__MAJOR, moduleGitCommit_, 11 | // Base=0% APY, Kink(80%)=20% APY Max=300% APY 12 | 0, 1681485479, 44415215206, 3435973836 13 | ) {} 14 | } 15 | -------------------------------------------------------------------------------- /contracts/modules/interest-rate-models/IRMClassMega.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../BaseIRMLinearKink.sol"; 6 | 7 | 8 | contract IRMClassMega is BaseIRMLinearKink { 9 | constructor(bytes32 moduleGitCommit_) 10 | BaseIRMLinearKink(MODULEID__IRM_CLASS__MEGA, moduleGitCommit_, 11 | // Base=0% APY, Kink(80%)=8% APY Max=200% APY 12 | 0, 709783723, 37689273223, 3435973836 13 | ) {} 14 | } 15 | -------------------------------------------------------------------------------- /contracts/modules/interest-rate-models/IRMClassMidCap.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../BaseIRMLinearKink.sol"; 6 | 7 | 8 | contract IRMClassMidCap is BaseIRMLinearKink { 9 | constructor(bytes32 moduleGitCommit_) 10 | BaseIRMLinearKink(MODULEID__IRM_CLASS__MIDCAP, moduleGitCommit_, 11 | // Base=0% APY, Kink(80%)=35% APY Max=300% APY 12 | 0, 2767755633, 40070134595, 3435973836 13 | ) {} 14 | } 15 | -------------------------------------------------------------------------------- /contracts/modules/interest-rate-models/IRMClassOHM.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../BaseIRMLinearKink.sol"; 6 | 7 | 8 | contract IRMClassOHM is BaseIRMLinearKink { 9 | constructor(bytes32 moduleGitCommit_) 10 | BaseIRMLinearKink(MODULEID__IRM_CLASS__OHM, moduleGitCommit_, 11 | // Base=5% APY, Kink(80%)=20% APY Max=300% APY 12 | 1546098748700444833, 1231511520, 44415215206, 3435973836 13 | ) {} 14 | } 15 | -------------------------------------------------------------------------------- /contracts/modules/interest-rate-models/IRMClassStable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../BaseIRMLinearKink.sol"; 6 | 7 | 8 | contract IRMClassStable is BaseIRMLinearKink { 9 | constructor(bytes32 moduleGitCommit_) 10 | BaseIRMLinearKink(MODULEID__IRM_CLASS__STABLE, moduleGitCommit_, 11 | // Base=0% APY, Kink(80%)=4% APY Max=100% APY 12 | 0, 361718388, 24123704987, 3435973836 13 | ) {} 14 | } 15 | -------------------------------------------------------------------------------- /contracts/modules/interest-rate-models/IRMClassUSDT.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../BaseIRMLinearKink.sol"; 6 | 7 | 8 | contract IRMClassUSDT is BaseIRMLinearKink { 9 | constructor(bytes32 moduleGitCommit_) 10 | BaseIRMLinearKink(MODULEID__IRM_CLASS__USDT, moduleGitCommit_, 11 | // Base=0% APY, Kink(80%)=7% APY Max=200% APY 12 | 0, 623991132, 38032443588, 3435973836 13 | ) {} 14 | } 15 | -------------------------------------------------------------------------------- /contracts/modules/interest-rate-models/IRMDefault.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../BaseIRMLinearKink.sol"; 6 | 7 | 8 | contract IRMDefault is BaseIRMLinearKink { 9 | constructor(bytes32 moduleGitCommit_) 10 | BaseIRMLinearKink(MODULEID__IRM_DEFAULT, moduleGitCommit_, 11 | // Base=0% APY, Kink(50%)=10% APY Max=300% APY 12 | 0, 1406417851, 19050045013, 2147483648 13 | ) {} 14 | } 15 | -------------------------------------------------------------------------------- /contracts/modules/interest-rate-models/test/IRMFixed.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../../BaseIRM.sol"; 6 | 7 | 8 | contract IRMFixed is BaseIRM { 9 | constructor(bytes32 moduleGitCommit_) BaseIRM(MODULEID__IRM_FIXED, moduleGitCommit_) {} 10 | 11 | function computeInterestRateImpl(address, uint32) internal override pure returns (int96) { 12 | return int96(int(uint(1e27 * 0.1) / (86400 * 365))); // not SECONDS_PER_YEAR to avoid breaking tests 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /contracts/modules/interest-rate-models/test/IRMLinear.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../../BaseIRM.sol"; 6 | 7 | 8 | contract IRMLinear is BaseIRM { 9 | constructor(bytes32 moduleGitCommit_) BaseIRM(MODULEID__IRM_LINEAR, moduleGitCommit_) {} 10 | 11 | uint internal constant MAX_IR = uint(1e27 * 0.1) / SECONDS_PER_YEAR; 12 | 13 | function computeInterestRateImpl(address, uint32 utilisation) internal override pure returns (int96) { 14 | return int96(int(MAX_IR * utilisation / type(uint32).max)); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /contracts/modules/interest-rate-models/test/IRMZero.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../../../BaseIRM.sol"; 6 | 7 | 8 | contract IRMZero is BaseIRM { 9 | constructor(bytes32 moduleGitCommit_) BaseIRM(MODULEID__IRM_ZERO, moduleGitCommit_) {} 10 | 11 | function computeInterestRateImpl(address, uint32) internal override pure returns (int96) { 12 | return 0; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /contracts/oracles/WBTCOracle.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | interface IChainlinkAggregatorV2V3 { 6 | function decimals() external view returns (uint8); 7 | function description() external view returns (string memory); 8 | function latestAnswer() external view returns (int256); 9 | function latestTimestamp() external view returns (uint256); 10 | } 11 | 12 | /// @notice Provides contract for fetching WBTC/ETH Chainlink price, using WBTC/BTC and BTC/ETH Chainlink oracles 13 | contract WBTCOracle is IChainlinkAggregatorV2V3 { 14 | address immutable public WBTCBTCChainlinkAggregator; 15 | address immutable public BTCETHChainlinkAggregator; 16 | 17 | constructor( 18 | address _WBTCBTCChainlinkAggregator, 19 | address _BTCETHChainlinkAggregator 20 | ) { 21 | // WBTCBTCChainlinkAggregator = "0xfdFD9C85aD200c506Cf9e21F1FD8dd01932FBB23"; 22 | // BTCETHChainlinkAggregator = "0xdeb288F737066589598e9214E782fa5A8eD689e8"; 23 | 24 | WBTCBTCChainlinkAggregator = _WBTCBTCChainlinkAggregator; 25 | BTCETHChainlinkAggregator = _BTCETHChainlinkAggregator; 26 | } 27 | 28 | function decimals() external pure override returns (uint8) { 29 | return 18; 30 | } 31 | 32 | function description() external pure override returns (string memory) { 33 | return "WBTC / ETH"; 34 | } 35 | 36 | /// @notice Get latest WBTC/BTC Chainlink feed timestamp 37 | /// @return timestamp latest WBTC/BTC Chainlink feed timestamp 38 | function latestTimestamp() external view override returns (uint256 timestamp) { 39 | return IChainlinkAggregatorV2V3(WBTCBTCChainlinkAggregator).latestTimestamp(); 40 | } 41 | 42 | /// @notice Get WBTC/ETH price. It does not check Chainlink oracles staleness! If staleness check needed, it's recommended to use latestTimestamp() functions on both Chainlink feeds used 43 | /// @return answer WBTC/ETH price or 0 if failure 44 | function latestAnswer() external view override returns (int256 answer) { 45 | // get the WBTC/BTC and BTC/ETH prices 46 | int256 WBTCBTCPrice = IChainlinkAggregatorV2V3(WBTCBTCChainlinkAggregator).latestAnswer(); 47 | int256 BTCETHPrice = IChainlinkAggregatorV2V3(BTCETHChainlinkAggregator).latestAnswer(); 48 | 49 | if (WBTCBTCPrice <= 0 || BTCETHPrice <= 0) return 0; 50 | 51 | // calculate WBTC/ETH price 52 | return WBTCBTCPrice * BTCETHPrice / 1e8; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /contracts/oracles/WSTETHOracle.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | 6 | interface IChainlinkAggregatorV2V3 { 7 | function decimals() external view returns (uint8); 8 | function description() external view returns (string memory); 9 | function latestAnswer() external view returns (int256); 10 | function latestTimestamp() external view returns (uint256); 11 | } 12 | 13 | interface IStETH { 14 | function getPooledEthByShares(uint256 _sharesAmount) external view returns (uint256); 15 | } 16 | 17 | /// @notice Provides wstETH/ETH price using stETH/ETH Chainlink oracle and wstETH/stETH exchange rate provided by stETH smart contract 18 | contract WSTETHOracle is IChainlinkAggregatorV2V3 { 19 | address immutable public stETH; 20 | address immutable public chainlinkAggregator; 21 | 22 | constructor( 23 | address _stETH, 24 | address _chainlinkAggregator 25 | ) { 26 | //stETH = 0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84; 27 | //chainlinkAggregator = 0x86392dC19c0b719886221c78AB11eb8Cf5c52812; 28 | 29 | stETH = _stETH; 30 | chainlinkAggregator = _chainlinkAggregator; 31 | } 32 | 33 | function decimals() external pure override returns (uint8) { 34 | return 18; 35 | } 36 | 37 | function description() external pure override returns (string memory) { 38 | return "WSTETH/ETH"; 39 | } 40 | 41 | function latestTimestamp() external view override returns (uint256) { 42 | return IChainlinkAggregatorV2V3(chainlinkAggregator).latestTimestamp(); 43 | } 44 | 45 | /// @notice Get wstETH/ETH price. It does not check Chainlink oracle staleness! If staleness check needed, it's recommended to use latestTimestamp() function 46 | /// @return answer wstETH/ETH price or 0 if failure 47 | function latestAnswer() external view override returns (int256 answer) { 48 | // get the stETH/ETH price from Chainlink oracle 49 | int256 stETHPrice = IChainlinkAggregatorV2V3(chainlinkAggregator).latestAnswer(); 50 | if (stETHPrice <= 0) return 0; 51 | 52 | // get wstETH/stETH exchange rate 53 | uint256 stEthPerWstETH = IStETH(stETH).getPooledEthByShares(1 ether); 54 | 55 | // calculate wstETH/ETH price 56 | return int256(stEthPerWstETH) * stETHPrice / 1e18; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /contracts/oracles/chainlinkBasedOracle.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | interface IChainlinkAggregatorV2V3 { 6 | function decimals() external view returns (uint8); 7 | function description() external view returns (string memory); 8 | function latestAnswer() external view returns (int256); 9 | function latestTimestamp() external view returns (uint256); 10 | } 11 | 12 | /// @notice Provides generic contract for fetching underlying/ETH Chainlink price, using underlying/USD and ETH/USD Chainlink oracles 13 | contract ChainlinkBasedOracle is IChainlinkAggregatorV2V3 { 14 | address immutable public underlyingUSDChainlinkAggregator; 15 | address immutable public ETHUSDChainlinkAggregator; 16 | string desc; 17 | 18 | constructor( 19 | address _underlyingUSDChainlinkAggregator, 20 | address _ETHUSDChainlinkAggregator, 21 | string memory _description 22 | ) { 23 | underlyingUSDChainlinkAggregator = _underlyingUSDChainlinkAggregator; 24 | ETHUSDChainlinkAggregator = _ETHUSDChainlinkAggregator; 25 | desc = _description; 26 | } 27 | 28 | function decimals() external pure override returns (uint8) { 29 | return 18; 30 | } 31 | 32 | function description() external view override returns (string memory) { 33 | return desc; 34 | } 35 | 36 | /// @notice Get latest underlying/USD Chainlink feed timestamp 37 | /// @return timestamp latest underlying/USD Chainlink feed timestamp 38 | function latestTimestamp() external view override returns (uint256 timestamp) { 39 | return IChainlinkAggregatorV2V3(underlyingUSDChainlinkAggregator).latestTimestamp(); 40 | } 41 | 42 | /// @notice Get underlying/ETH price. It does not check Chainlink oracles staleness! If staleness check needed, it's recommended to use latestTimestamp() functions on both Chainlink feeds used 43 | /// @return answer underlying/ETH price or 0 if failure 44 | function latestAnswer() external view override returns (int256 answer) { 45 | // get the ETH/USD and underlying/USD prices 46 | int256 ETHUSDPrice = IChainlinkAggregatorV2V3(ETHUSDChainlinkAggregator).latestAnswer(); 47 | int256 underlyingUSDPrice = IChainlinkAggregatorV2V3(underlyingUSDChainlinkAggregator).latestAnswer(); 48 | 49 | if (ETHUSDPrice <= 0 || underlyingUSDPrice <= 0) return 0; 50 | 51 | // calculate underlying/ETH price 52 | return underlyingUSDPrice * 1e18 / ETHUSDPrice; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /contracts/swapHandlers/ISwapHandler.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | interface ISwapHandler { 6 | /// @notice Params for swaps using SwapHub contract and swap handlers 7 | /// @param underlyingIn sold token address 8 | /// @param underlyingOut bought token address 9 | /// @param mode type of the swap: 0 for exact input, 1 for exact output 10 | /// @param amountIn amount of token to sell. Exact value for exact input, maximum for exact output 11 | /// @param amountOut amount of token to buy. Exact value for exact output, minimum for exact input 12 | /// @param exactOutTolerance Maximum difference between requested amountOut and received tokens in exact output swap. Ignored for exact input 13 | /// @param payload multi-purpose byte param. The usage depends on the swap handler implementation 14 | struct SwapParams { 15 | address underlyingIn; 16 | address underlyingOut; 17 | uint mode; // 0=exactIn 1=exactOut 18 | uint amountIn; // mode 0: exact, mode 1: maximum 19 | uint amountOut; // mode 0: minimum, mode 1: exact 20 | uint exactOutTolerance; // mode 0: ignored, mode 1: downward tolerance on amountOut (fee-on-transfer etc.) 21 | bytes payload; 22 | } 23 | 24 | /// @notice Execute a trade on the swap handler 25 | /// @param params struct defining the requested trade 26 | function executeSwap(SwapParams calldata params) external; 27 | } 28 | -------------------------------------------------------------------------------- /contracts/swapHandlers/SwapHandler1Inch.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "./SwapHandlerCombinedBase.sol"; 6 | 7 | /// @notice Swap handler executing trades on 1Inch 8 | contract SwapHandler1Inch is SwapHandlerCombinedBase { 9 | address immutable public oneInchAggregator; 10 | 11 | constructor(address oneInchAggregator_, address uniSwapRouterV2, address uniSwapRouterV3) SwapHandlerCombinedBase(uniSwapRouterV2, uniSwapRouterV3) { 12 | oneInchAggregator = oneInchAggregator_; 13 | } 14 | 15 | function swapPrimary(SwapParams memory params) override internal returns (uint amountOut) { 16 | setMaxAllowance(params.underlyingIn, params.amountIn, oneInchAggregator); 17 | 18 | (bool success, bytes memory result) = oneInchAggregator.call(params.payload); 19 | if (!success) revertBytes(result); 20 | 21 | // return amount out reported by 1Inch. It might not be exact for fee-on-transfer or rebasing tokens. 22 | amountOut = abi.decode(result, (uint)); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /contracts/swapHandlers/SwapHandlerBase.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "./ISwapHandler.sol"; 6 | import "../Interfaces.sol"; 7 | import "../Utils.sol"; 8 | 9 | /// @notice Base contract for swap handlers 10 | abstract contract SwapHandlerBase is ISwapHandler { 11 | function trySafeApprove(address token, address to, uint value) internal returns (bool, bytes memory) { 12 | (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.approve.selector, to, value)); 13 | return (success && (data.length == 0 || abi.decode(data, (bool))), data); 14 | } 15 | 16 | function safeApproveWithRetry(address token, address to, uint value) internal { 17 | (bool success, bytes memory data) = trySafeApprove(token, to, value); 18 | 19 | // some tokens, like USDT, require the allowance to be set to 0 first 20 | if (!success) { 21 | (success,) = trySafeApprove(token, to, 0); 22 | if (success) { 23 | (success,) = trySafeApprove(token, to, value); 24 | } 25 | } 26 | 27 | if (!success) revertBytes(data); 28 | } 29 | 30 | function transferBack(address token) internal { 31 | uint balance = IERC20(token).balanceOf(address(this)); 32 | if (balance > 0) Utils.safeTransfer(token, msg.sender, balance); 33 | } 34 | 35 | function setMaxAllowance(address token, uint minAllowance, address spender) internal { 36 | uint allowance = IERC20(token).allowance(address(this), spender); 37 | if (allowance < minAllowance) safeApproveWithRetry(token, spender, type(uint).max); 38 | } 39 | 40 | function revertBytes(bytes memory errMsg) internal pure { 41 | if (errMsg.length > 0) { 42 | assembly { 43 | revert(add(32, errMsg), mload(errMsg)) 44 | } 45 | } 46 | 47 | revert("SwapHandlerBase: empty error"); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /contracts/swapHandlers/SwapHandlerCombinedBase.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "./SwapHandlerBase.sol"; 6 | import "../vendor/ISwapRouterV3.sol"; 7 | import "../vendor/ISwapRouterV2.sol"; 8 | 9 | /// @notice Base contract for swap handlers which execute a secondary swap on Uniswap V2 or V3 for exact output 10 | abstract contract SwapHandlerCombinedBase is SwapHandlerBase { 11 | address immutable public uniSwapRouterV2; 12 | address immutable public uniSwapRouterV3; 13 | 14 | constructor(address uniSwapRouterV2_, address uniSwapRouterV3_) { 15 | uniSwapRouterV2 = uniSwapRouterV2_; 16 | uniSwapRouterV3 = uniSwapRouterV3_; 17 | } 18 | 19 | function executeSwap(SwapParams memory params) external override { 20 | require(params.mode <= 1, "SwapHandlerCombinedBase: invalid mode"); 21 | 22 | if (params.mode == 0) { 23 | swapPrimary(params); 24 | } else { 25 | // For exact output expect a payload for the primary swap provider and a path to swap the remainder on Uni2 or Uni3 26 | bytes memory path; 27 | (params.payload, path) = abi.decode(params.payload, (bytes, bytes)); 28 | 29 | uint primaryAmountOut = swapPrimary(params); 30 | 31 | if (primaryAmountOut < params.amountOut) { 32 | // The path param is reused for UniV2 and UniV3 swaps. The protocol to use is determined by the path length. 33 | // The length of valid UniV2 paths is given as n * 20, for n > 1, and the shortes path is 40 bytes. 34 | // The length of valid UniV3 paths is given as 20 + n * 23 for n > 0, because of an additional 3 bytes for the pool fee. 35 | // The max path length must be lower than the first path length which is valid for both protocols (and is therefore ambiguous) 36 | // This value is at 20 UniV3 hops, which corresponds to 24 UniV2 hops. 37 | require(path.length >= 40 && path.length < 20 + (20 * 23), "SwapHandlerPayloadBase: secondary path format"); 38 | 39 | uint remainder; 40 | unchecked { remainder = params.amountOut - primaryAmountOut; } 41 | 42 | swapExactOutDirect(params, remainder, path); 43 | } 44 | } 45 | 46 | transferBack(params.underlyingIn); 47 | } 48 | 49 | function swapPrimary(SwapParams memory params) internal virtual returns (uint amountOut); 50 | 51 | function swapExactOutDirect(SwapParams memory params, uint amountOut, bytes memory path) private { 52 | (bool isUniV2, address[] memory uniV2Path) = detectAndDecodeUniV2Path(path); 53 | 54 | if (isUniV2) { 55 | setMaxAllowance(params.underlyingIn, params.amountIn, uniSwapRouterV2); 56 | 57 | ISwapRouterV2(uniSwapRouterV2).swapTokensForExactTokens(amountOut, type(uint).max, uniV2Path, msg.sender, block.timestamp); 58 | } else { 59 | setMaxAllowance(params.underlyingIn, params.amountIn, uniSwapRouterV3); 60 | 61 | ISwapRouterV3(uniSwapRouterV3).exactOutput( 62 | ISwapRouterV3.ExactOutputParams({ 63 | path: path, 64 | recipient: msg.sender, 65 | amountOut: amountOut, 66 | amountInMaximum: type(uint).max, 67 | deadline: block.timestamp 68 | }) 69 | ); 70 | } 71 | } 72 | 73 | function detectAndDecodeUniV2Path(bytes memory path) private pure returns (bool, address[] memory) { 74 | bool isUniV2 = path.length % 20 == 0; 75 | address[] memory addressPath; 76 | 77 | if (isUniV2) { 78 | uint addressPathSize = path.length / 20; 79 | addressPath = new address[](addressPathSize); 80 | 81 | unchecked { 82 | for(uint i = 0; i < addressPathSize; ++i) { 83 | addressPath[i] = toAddress(path, i * 20); 84 | } 85 | } 86 | } 87 | 88 | return (isUniV2, addressPath); 89 | } 90 | 91 | function toAddress(bytes memory data, uint start) private pure returns (address result) { 92 | // assuming data length is already validated 93 | assembly { 94 | // borrowed from BytesLib https://github.com/GNSPS/solidity-bytes-utils/blob/master/contracts/BytesLib.sol 95 | result := div(mload(add(add(data, 0x20), start)), 0x1000000000000000000000000) 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /contracts/swapHandlers/SwapHandlerUniAutoRouter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "./SwapHandlerCombinedBase.sol"; 6 | 7 | /// @notice Swap handler executing trades on Uniswap with a payload generated by auto-router 8 | contract SwapHandlerUniAutoRouter is SwapHandlerCombinedBase { 9 | address immutable public uniSwapRouter02; 10 | 11 | constructor(address uniSwapRouter02_, address uniSwapRouterV2, address uniSwapRouterV3) SwapHandlerCombinedBase(uniSwapRouterV2, uniSwapRouterV3) { 12 | uniSwapRouter02 = uniSwapRouter02_; 13 | } 14 | 15 | function swapPrimary(SwapParams memory params) override internal returns (uint amountOut) { 16 | setMaxAllowance(params.underlyingIn, params.amountIn, uniSwapRouter02); 17 | 18 | if (params.mode == 0) { 19 | // for exact input return value is ignored 20 | swapInternal(params); 21 | } else { 22 | // exact output on SwapRouter02 routed through uniV2 is not exact, balance check is needed 23 | uint preBalance = IERC20(params.underlyingOut).balanceOf(msg.sender); 24 | 25 | swapInternal(params); 26 | 27 | uint postBalance = IERC20(params.underlyingOut).balanceOf(msg.sender); 28 | 29 | require(postBalance >= preBalance, "SwapHandlerUniAutoRouter: negative amount out"); 30 | 31 | unchecked { amountOut = postBalance - preBalance; } 32 | } 33 | } 34 | 35 | function swapInternal(SwapParams memory params) private { 36 | (bool success, bytes memory result) = uniSwapRouter02.call(params.payload); 37 | if (!success) revertBytes(result); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /contracts/swapHandlers/SwapHandlerUniswapV3.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "./SwapHandlerBase.sol"; 6 | import "../vendor/ISwapRouterV3.sol"; 7 | 8 | /// @notice Swap handler executing trades on UniswapV3 through SwapRouter 9 | contract SwapHandlerUniswapV3 is SwapHandlerBase { 10 | address immutable public uniSwapRouterV3; 11 | 12 | constructor(address uniSwapRouterV3_) { 13 | uniSwapRouterV3 = uniSwapRouterV3_; 14 | } 15 | 16 | function executeSwap(SwapParams calldata params) override external { 17 | require(params.mode <= 1, "SwapHandlerUniswapV3: invalid mode"); 18 | 19 | setMaxAllowance(params.underlyingIn, params.amountIn, uniSwapRouterV3); 20 | 21 | // The payload in SwapParams has double use. For single pool swaps, the price limit and a pool fee are abi-encoded as 2 uints, where bytes length is 64. 22 | // For multi-pool swaps, the payload represents a swap path. A valid path is a packed encoding of tokenIn, pool fee and tokenOut. 23 | // The valid path lengths are therefore: 20 + n*(3 + 20), where n >= 1, and no valid path can be 64 bytes long. 24 | if (params.payload.length == 64) { 25 | (uint sqrtPriceLimitX96, uint fee) = abi.decode(params.payload, (uint, uint)); 26 | if (params.mode == 0) 27 | exactInputSingle(params, sqrtPriceLimitX96, fee); 28 | else 29 | exactOutputSingle(params, sqrtPriceLimitX96, fee); 30 | } else { 31 | if (params.mode == 0) 32 | exactInput(params, params.payload); 33 | else 34 | exactOutput(params, params.payload); 35 | } 36 | 37 | if (params.mode == 1) transferBack(params.underlyingIn); 38 | } 39 | 40 | function exactInputSingle(SwapParams memory params, uint sqrtPriceLimitX96, uint fee) private { 41 | ISwapRouterV3(uniSwapRouterV3).exactInputSingle( 42 | ISwapRouterV3.ExactInputSingleParams({ 43 | tokenIn: params.underlyingIn, 44 | tokenOut: params.underlyingOut, 45 | fee: uint24(fee), 46 | recipient: msg.sender, 47 | deadline: block.timestamp, 48 | amountIn: params.amountIn, 49 | amountOutMinimum: params.amountOut, 50 | sqrtPriceLimitX96: uint160(sqrtPriceLimitX96) 51 | }) 52 | ); 53 | } 54 | 55 | function exactInput(SwapParams memory params, bytes memory path) private { 56 | ISwapRouterV3(uniSwapRouterV3).exactInput( 57 | ISwapRouterV3.ExactInputParams({ 58 | path: path, 59 | recipient: msg.sender, 60 | deadline: block.timestamp, 61 | amountIn: params.amountIn, 62 | amountOutMinimum: params.amountOut 63 | }) 64 | ); 65 | } 66 | 67 | function exactOutputSingle(SwapParams memory params, uint sqrtPriceLimitX96, uint fee) private { 68 | ISwapRouterV3(uniSwapRouterV3).exactOutputSingle( 69 | ISwapRouterV3.ExactOutputSingleParams({ 70 | tokenIn: params.underlyingIn, 71 | tokenOut: params.underlyingOut, 72 | fee: uint24(fee), 73 | recipient: msg.sender, 74 | deadline: block.timestamp, 75 | amountOut: params.amountOut, 76 | amountInMaximum: params.amountIn, 77 | sqrtPriceLimitX96: uint160(sqrtPriceLimitX96) 78 | }) 79 | ); 80 | } 81 | 82 | function exactOutput(SwapParams memory params, bytes memory path) private { 83 | ISwapRouterV3(uniSwapRouterV3).exactOutput( 84 | ISwapRouterV3.ExactOutputParams({ 85 | path: path, 86 | recipient: msg.sender, 87 | deadline: block.timestamp, 88 | amountOut: params.amountOut, 89 | amountInMaximum: params.amountIn 90 | }) 91 | ); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /contracts/test/FlashLoanAdaptorTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../Interfaces.sol"; 6 | 7 | contract FlashLoanAdaptorTest is IERC3156FlashBorrower { 8 | 9 | event BorrowResult(address token, uint balance, uint fee, uint borrowIndex, address sender, address initiator); 10 | 11 | function setMaxAllowance(address token, address to) public returns (bool success) { 12 | (success,) = token.call(abi.encodeWithSelector(IERC20.approve.selector, to, type(uint).max)); 13 | } 14 | 15 | function testFlashBorrow(address lender, address[] calldata receivers, address[] calldata tokens, uint[] calldata amounts) external { 16 | bytes memory data = abi.encode(receivers, tokens, amounts, 0); 17 | 18 | _borrow(lender, receivers[0], tokens[0], amounts[0], data); 19 | 20 | for (uint i = 0; i < receivers.length; ++i) { 21 | for (uint j = 0; j < tokens.length; ++j) { 22 | assert(IERC20(tokens[j]).balanceOf(receivers[i]) == 0); 23 | } 24 | } 25 | } 26 | 27 | function onFlashLoan(address initiator, address token, uint256, uint256 fee, bytes calldata data) override external returns(bytes32) { 28 | (address[] memory receivers, address[] memory tokens, uint[] memory amounts, uint borrowIndex) = 29 | abi.decode(data, (address[], address[], uint[], uint)); 30 | 31 | setMaxAllowance(token, msg.sender); 32 | 33 | _emitBorrowResult(token, fee, borrowIndex, initiator); 34 | 35 | if(tokens.length > 0 && borrowIndex < tokens.length - 1) { 36 | uint nextBorrowIndex = borrowIndex + 1; 37 | _borrow( 38 | msg.sender, 39 | receivers[nextBorrowIndex], 40 | tokens[nextBorrowIndex], 41 | amounts[nextBorrowIndex], 42 | abi.encode(receivers, tokens, amounts, nextBorrowIndex) 43 | ); 44 | } 45 | 46 | return keccak256("ERC3156FlashBorrower.onFlashLoan"); 47 | } 48 | 49 | function _borrow(address lender, address receiver, address token, uint amount, bytes memory data) internal { 50 | IERC3156FlashLender(lender).flashLoan(IERC3156FlashBorrower(receiver), token, amount, data); 51 | } 52 | 53 | function _emitBorrowResult(address token, uint fee, uint borrowIndex, address initiator) internal { 54 | emit BorrowResult( 55 | token, 56 | IERC20(token).balanceOf(address(this)), 57 | fee, 58 | borrowIndex, 59 | msg.sender, 60 | initiator 61 | ); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /contracts/test/FlashLoanAdaptorTest2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | 6 | import "./FlashLoanAdaptorTest.sol"; 7 | 8 | contract FlashLoanAdaptorTest2 is FlashLoanAdaptorTest {} 9 | -------------------------------------------------------------------------------- /contracts/test/FlashLoanNativeTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../Interfaces.sol"; 6 | import "../modules/DToken.sol"; 7 | import "../modules/Markets.sol"; 8 | import "../modules/Exec.sol"; 9 | import "../modules/DToken.sol"; 10 | 11 | 12 | contract FlashLoanNativeTest is IDeferredLiquidityCheck { 13 | struct CallbackData { 14 | address eulerAddr; 15 | address marketsAddr; 16 | address execAddr; 17 | address underlying; 18 | uint amount; 19 | bool payItBack; 20 | } 21 | 22 | function testFlashLoan(CallbackData calldata data) external { 23 | Exec(data.execAddr).deferLiquidityCheck(address(this), abi.encode(data)); 24 | } 25 | 26 | function onDeferredLiquidityCheck(bytes memory encodedData) external override { 27 | CallbackData memory data = abi.decode(encodedData, (CallbackData)); 28 | 29 | address dTokenAddr = Markets(data.marketsAddr).underlyingToDToken(data.underlying); 30 | DToken dToken = DToken(dTokenAddr); 31 | 32 | dToken.borrow(0, data.amount); 33 | 34 | require(IERC20(data.underlying).balanceOf(address(this)) == data.amount, "didn't receive underlying"); 35 | require(dToken.balanceOf(address(this)) == data.amount, "didn't receive dTokens"); 36 | 37 | if (data.payItBack) { 38 | IERC20(data.underlying).approve(data.eulerAddr, type(uint).max); 39 | dToken.repay(0, data.amount); 40 | 41 | require(IERC20(data.underlying).balanceOf(address(this)) == 0, "didn't repay underlying"); 42 | require(dToken.balanceOf(address(this)) == 0, "didn't burn dTokens"); 43 | } 44 | } 45 | 46 | function testFlashLoan2(address underlying, address dTokenAddr, address eulerAddr, uint amount, uint repayAmount) external { 47 | DToken(dTokenAddr).flashLoan(amount, abi.encode(underlying, eulerAddr, repayAmount)); 48 | } 49 | 50 | function onFlashLoan(bytes calldata data) external { 51 | (address underlying, address eulerAddr, uint repayAmount) = abi.decode(data, (address, address, uint)); 52 | IERC20(underlying).transfer(eulerAddr, repayAmount); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /contracts/test/InvariantChecker.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | import "hardhat/console.sol"; 5 | 6 | import "../Constants.sol"; 7 | import "../Euler.sol"; 8 | import "../modules/Markets.sol"; 9 | import "../modules/EToken.sol"; 10 | import "../modules/DToken.sol"; 11 | 12 | // This is a testing-only contract that verifies invariants of the Euler system. 13 | 14 | struct LocalVars { 15 | uint eTokenBalances; 16 | uint dTokenBalances; 17 | uint dTokenBalancesExact; 18 | 19 | uint numUsersWithEtokens; 20 | uint numUsersWithDtokens; 21 | 22 | uint eTokenTotalSupply; 23 | uint reserveBalance; 24 | uint dTokenTotalSupply; 25 | uint dTokenTotalSupplyExact; 26 | 27 | uint poolSize; 28 | } 29 | 30 | contract InvariantChecker is Constants { 31 | function check(address eulerContract, address[] calldata markets, address[] calldata accounts, bool verbose) external view { 32 | Euler eulerProxy = Euler(eulerContract); 33 | Markets marketsProxy = Markets(eulerProxy.moduleIdToProxy(MODULEID__MARKETS)); 34 | 35 | LocalVars memory v; 36 | 37 | for (uint i = 0; i < markets.length; ++i) { 38 | IERC20 eToken = IERC20(marketsProxy.underlyingToEToken(markets[i])); 39 | IERC20 dToken = IERC20(marketsProxy.eTokenToDToken(address(eToken))); 40 | 41 | v.eTokenBalances = 0; 42 | v.dTokenBalances = 0; 43 | v.dTokenBalancesExact = 0; 44 | 45 | v.numUsersWithEtokens = 0; 46 | v.numUsersWithDtokens = 0; 47 | 48 | for (uint j = 0; j < accounts.length; ++j) { 49 | address account = accounts[j]; 50 | 51 | { 52 | uint bal = eToken.balanceOf(account); 53 | v.eTokenBalances += bal; 54 | if (bal != 0) v.numUsersWithEtokens++; 55 | } 56 | 57 | { 58 | uint bal = dToken.balanceOf(account); 59 | v.dTokenBalances += bal; 60 | if (bal != 0) v.numUsersWithDtokens++; 61 | } 62 | 63 | { 64 | uint bal = DToken(address(dToken)).balanceOfExact(account); 65 | v.dTokenBalancesExact += bal; 66 | } 67 | } 68 | 69 | v.eTokenTotalSupply = eToken.totalSupply(); 70 | v.reserveBalance = EToken(address(eToken)).reserveBalance(); 71 | v.dTokenTotalSupply = dToken.totalSupply(); 72 | v.dTokenTotalSupplyExact = DToken(address(dToken)).totalSupplyExact(); 73 | 74 | v.poolSize = IERC20(markets[i]).balanceOf(eulerContract); 75 | 76 | if (verbose) { 77 | console.log("--------------------------------------------------------------"); 78 | console.log("MARKET = ", markets[i]); 79 | console.log("POOL SIZE = ", v.poolSize); 80 | console.log(""); 81 | console.log("USERS WITH ETOKENS = ", v.numUsersWithEtokens); 82 | console.log("ETOKEN BALANCE SUM = ", v.eTokenBalances); 83 | console.log("RESERVE BALANCE = ", v.reserveBalance); 84 | console.log("ETOKEN TOTAL SUPPLY = ", v.eTokenTotalSupply); 85 | console.log(""); 86 | console.log("USERS WITH DTOKENS = ", v.numUsersWithDtokens); 87 | console.log("DTOKEN BALANCE SUM = ", v.dTokenBalances); 88 | console.log("DTOKEN TOTAL SUPPLY = ", v.dTokenTotalSupply); 89 | console.log("DTOKEN BALEXACT SUM = ", v.dTokenBalancesExact); 90 | console.log("DTOKEN EXACT SUPPLY = ", v.dTokenTotalSupplyExact); 91 | } 92 | 93 | require(v.eTokenBalances + v.reserveBalance == v.eTokenTotalSupply, "invariant checker: eToken balance mismatch"); 94 | 95 | // Due to rounding, user debt balances can grow slightly faster than the total debt supply 96 | require(v.dTokenBalancesExact >= v.dTokenTotalSupplyExact, "invariant checker: dToken exact balance mismatch"); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /contracts/test/JunkETokenUpgrade.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../BaseLogic.sol"; 6 | 7 | 8 | contract JunkETokenUpgrade is BaseLogic { 9 | constructor() BaseLogic(MODULEID__ETOKEN, bytes32(0)) {} 10 | 11 | function name() external pure returns (string memory) { 12 | return "JUNK_UPGRADE_NAME"; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /contracts/test/JunkMarketsUpgrade.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../BaseLogic.sol"; 6 | 7 | 8 | contract JunkMarketsUpgrade is BaseLogic { 9 | constructor() BaseLogic(MODULEID__MARKETS, bytes32(uint(0x1234))) {} 10 | 11 | function getEnteredMarkets(address) external pure returns (address[] memory output) { 12 | output; 13 | require(false, "JUNK_UPGRADE_TEST_FAILURE"); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /contracts/test/MockEACAggregatorProxy.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | contract MockAggregatorProxy { 5 | struct Data { 6 | uint80 roundId; // The round ID. 7 | int256 answer; // The price. 8 | uint256 timestamp; // Timestamp of when the round was updated. 9 | uint256 roundTimestamp; // Timestamp of when the round started. 10 | uint80 answeredInRound; // The round ID of the round in which the answer was computed. 11 | } 12 | 13 | string constant description_ = "Mock Aggregator"; 14 | uint256 constant version_ = 1; 15 | uint8 public decimals_; 16 | uint80 public currentRoundId_; 17 | mapping(uint256 => Data) public data_; 18 | 19 | constructor(uint8 _decimals) { 20 | decimals_ = _decimals; 21 | } 22 | 23 | function mockSetData(Data calldata data) external { 24 | data_[data.roundId] = data; 25 | currentRoundId_ = data.roundId; 26 | } 27 | 28 | function mockSetValidAnswer(int256 answer) external { 29 | currentRoundId_++; 30 | data_[currentRoundId_] = 31 | Data( 32 | currentRoundId_, 33 | answer, 34 | block.timestamp, 35 | block.timestamp, 36 | currentRoundId_ 37 | ); 38 | } 39 | 40 | function latestAnswer() external view returns (int256) { 41 | return data_[currentRoundId_].answer; 42 | } 43 | 44 | function latestTimestamp() external view returns (uint256) { 45 | return data_[currentRoundId_].timestamp; 46 | } 47 | 48 | function latestRound() external view returns (uint256) { 49 | return currentRoundId_; 50 | } 51 | 52 | function getAnswer(uint256 roundId) external view returns (int256) { 53 | return data_[roundId].answer; 54 | } 55 | 56 | function getTimestamp(uint256 roundId) external view returns (uint256) { 57 | return data_[roundId].timestamp; 58 | } 59 | 60 | function decimals() external view returns (uint8) { 61 | return decimals_; 62 | } 63 | 64 | function description() external pure returns (string memory) { 65 | return description_; 66 | } 67 | 68 | function version() external pure returns (uint256) { 69 | return version_; 70 | } 71 | 72 | function getRoundData(uint80 _roundId) external view 73 | returns ( 74 | uint80 roundId, 75 | int256 answer, 76 | uint256 startedAt, 77 | uint256 updatedAt, 78 | uint80 answeredInRound 79 | ) { 80 | Data memory result = data_[_roundId]; 81 | return ( 82 | result.roundId, 83 | result.answer, 84 | result.timestamp, 85 | result.roundTimestamp, 86 | result.answeredInRound 87 | ); 88 | } 89 | 90 | function latestRoundData() external view 91 | returns ( 92 | uint80 roundId, 93 | int256 answer, 94 | uint256 startedAt, 95 | uint256 updatedAt, 96 | uint80 answeredInRound 97 | ) { 98 | Data memory result = data_[currentRoundId_]; 99 | return ( 100 | result.roundId, 101 | result.answer, 102 | result.timestamp, 103 | result.roundTimestamp, 104 | result.answeredInRound 105 | ); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /contracts/test/MockStETH.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | contract MockStETH { 5 | bool shouldRevert = false; 6 | uint256 pooledEthByShares; 7 | uint256 sharesByPooledEth; 8 | 9 | function mockSetRevert(bool _shouldRevert) external { 10 | shouldRevert = _shouldRevert; 11 | } 12 | 13 | function mockSetData(uint256 _pooledEthByShares, uint256 _sharesByPooledEth) external { 14 | pooledEthByShares = _pooledEthByShares; 15 | sharesByPooledEth = _sharesByPooledEth; 16 | } 17 | 18 | function getPooledEthByShares(uint256 _sharesAmount) external view returns (uint256) { 19 | if (shouldRevert) revert(""); 20 | _sharesAmount = _sharesAmount; 21 | return pooledEthByShares; 22 | } 23 | 24 | function getSharesByPooledEth(uint256 _pooledEthAmount) external view returns (uint256) { 25 | if (shouldRevert) revert(""); 26 | _pooledEthAmount = _pooledEthAmount; 27 | return sharesByPooledEth; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /contracts/test/MockUniswapV3Factory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | import "./MockUniswapV3Pool.sol"; 5 | 6 | contract MockUniswapV3Factory { 7 | mapping(address => mapping(address => mapping(uint24 => address))) public getPool; 8 | 9 | struct Parameters { 10 | address factory; 11 | address token0; 12 | address token1; 13 | uint24 fee; 14 | int24 tickSpacing; 15 | } 16 | 17 | Parameters public parameters; 18 | 19 | function deploy(address factory, address token0, address token1, uint24 fee, int24 tickSpacing) internal returns (address pool) { 20 | parameters = Parameters({factory: factory, token0: token0, token1: token1, fee: fee, tickSpacing: tickSpacing}); 21 | pool = address(new MockUniswapV3Pool{salt: keccak256(abi.encode(token0, token1, fee))}()); 22 | delete parameters; 23 | } 24 | 25 | function createPool(address tokenA, address tokenB, uint24 fee) external returns (address pool) { 26 | require(tokenA != tokenB); 27 | (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); 28 | require(token0 != address(0)); 29 | require(getPool[token0][token1][fee] == address(0)); 30 | 31 | pool = deploy(address(this), token0, token1, fee, 0); 32 | getPool[token0][token1][fee] = pool; 33 | getPool[token1][token0][fee] = pool; 34 | } 35 | 36 | function setPoolAddress(address tokenA, address tokenB, uint24 fee, address newAddress) external { 37 | (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); 38 | getPool[token0][token1][fee] = newAddress; 39 | getPool[token1][token0][fee] = newAddress; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /contracts/test/MockUniswapV3Pool.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | import "../vendor/TickMath.sol"; 5 | 6 | interface IUniswapV3PoolDeployer { 7 | function parameters() external view returns ( 8 | address factory, 9 | address token0, 10 | address token1, 11 | uint24 fee, 12 | int24 tickSpacing 13 | ); 14 | } 15 | 16 | contract MockUniswapV3Pool { 17 | address public immutable factory; 18 | address public immutable token0; 19 | address public immutable token1; 20 | uint24 public immutable fee; 21 | 22 | constructor() { 23 | (factory, token0, token1, fee,) = IUniswapV3PoolDeployer(msg.sender).parameters(); 24 | } 25 | 26 | 27 | 28 | 29 | 30 | uint160 currSqrtPriceX96; 31 | int24 currTwap; 32 | bool throwOld; 33 | bool throwNotInitiated; 34 | bool throwOther; 35 | bool throwEmpty; 36 | 37 | function mockSetTwap(uint160 sqrtPriceX96) public { 38 | currSqrtPriceX96 = sqrtPriceX96; 39 | currTwap = TickMath.getTickAtSqrtRatio(sqrtPriceX96); 40 | } 41 | 42 | function initialize(uint160 sqrtPriceX96) external { 43 | mockSetTwap(sqrtPriceX96); 44 | } 45 | 46 | function mockSetThrowOld(bool val) external { 47 | throwOld = val; 48 | } 49 | 50 | function mockSetThrowNotInitiated(bool val) external { 51 | throwNotInitiated = val; 52 | } 53 | 54 | function mockSetThrowOther(bool val) external { 55 | throwOther = val; 56 | } 57 | 58 | function mockSetThrowEmpty(bool val) external { 59 | throwEmpty = val; 60 | } 61 | 62 | 63 | function observations(uint256) external pure returns (uint32, int56, uint160, bool) { 64 | return (0, 0, 0, true); 65 | } 66 | 67 | function slot0() external view returns (uint160 sqrtPriceX96, int24 tick, uint16 observationIndex, uint16 observationCardinality, uint16 observationCardinalityNext, uint8 feeProtocol, bool unlocked) { 68 | sqrtPriceX96 = currSqrtPriceX96; 69 | 70 | // These fields are tested with the real uniswap core contracts: 71 | observationIndex = observationCardinality = observationCardinalityNext = 1; 72 | 73 | // Not used in Euler tests: 74 | tick = 0; 75 | feeProtocol = 0; 76 | unlocked = false; 77 | } 78 | 79 | function observe(uint32[] calldata secondsAgos) external view returns (int56[] memory tickCumulatives, uint160[] memory liquidityCumulatives) { 80 | require(!throwOld, "OLD"); 81 | require (!throwOther, "OTHER"); 82 | 83 | require(secondsAgos.length == 2, "uniswap-pool-mock/unsupported-args-1"); 84 | require(secondsAgos[1] == 0, "uniswap-pool-mock/unsupported-args-2"); 85 | require(secondsAgos[0] > 0, "uniswap-pool-mock/unsupported-args-3"); 86 | 87 | tickCumulatives = new int56[](2); 88 | liquidityCumulatives = new uint160[](2); 89 | 90 | tickCumulatives[0] = 0; 91 | tickCumulatives[1] = int56(currTwap) * int56(uint56(secondsAgos[0])); 92 | liquidityCumulatives[0] = liquidityCumulatives[1] = 0; 93 | } 94 | 95 | 96 | 97 | function increaseObservationCardinalityNext(uint16) external { 98 | // This function is tested with the real uniswap core contracts 99 | require (!throwNotInitiated, "LOK"); 100 | require (!throwOther, "OTHER"); 101 | require (!throwEmpty); 102 | throwNotInitiated = throwNotInitiated; // suppress visibility warning 103 | } 104 | 105 | 106 | uint128 public liquidity = 100; 107 | 108 | function mockSetLiquidity(uint128 newLiquidity) external { 109 | liquidity = newLiquidity; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /contracts/test/SimpleUniswapPeriphery.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | // Simplest possible periphery implementation, used for testing integration with the 5 | // real uniswap core contracts. 6 | 7 | // Adapted from uniswap-v3-periphery/contracts/test/TestUniswapV3Callee.sol 8 | 9 | import "../Interfaces.sol"; 10 | 11 | interface IUniswapV3Pool { 12 | function token0() external view returns (address); 13 | function token1() external view returns (address); 14 | function mint(address recipient, int24 tickLower, int24 tickUpper, uint128 amount, bytes calldata data) external returns (uint256 amount0, uint256 amount1); 15 | function swap(address recipient, bool zeroForOne, int256 amountSpecified, uint160 sqrtPriceLimitX96, bytes calldata data) external returns (int256 amount0, int256 amount1); 16 | } 17 | 18 | contract SimpleUniswapPeriphery { 19 | function swapExact0For1( 20 | address pool, 21 | uint256 amount0In, 22 | address recipient, 23 | uint160 sqrtPriceLimitX96 24 | ) external { 25 | IUniswapV3Pool(pool).swap(recipient, true, int(amount0In), sqrtPriceLimitX96, abi.encode(msg.sender)); 26 | } 27 | 28 | function swap0ForExact1( 29 | address pool, 30 | uint256 amount1Out, 31 | address recipient, 32 | uint160 sqrtPriceLimitX96 33 | ) external { 34 | IUniswapV3Pool(pool).swap(recipient, true, -int(amount1Out), sqrtPriceLimitX96, abi.encode(msg.sender)); 35 | } 36 | 37 | function swapExact1For0( 38 | address pool, 39 | uint256 amount1In, 40 | address recipient, 41 | uint160 sqrtPriceLimitX96 42 | ) external { 43 | IUniswapV3Pool(pool).swap(recipient, false, int(amount1In), sqrtPriceLimitX96, abi.encode(msg.sender)); 44 | } 45 | 46 | function swap1ForExact0( 47 | address pool, 48 | uint256 amount0Out, 49 | address recipient, 50 | uint160 sqrtPriceLimitX96 51 | ) external { 52 | IUniswapV3Pool(pool).swap(recipient, false, int(amount0Out), sqrtPriceLimitX96, abi.encode(msg.sender)); 53 | } 54 | 55 | function swapToLowerSqrtPrice( 56 | address pool, 57 | uint160 sqrtPriceX96, 58 | address recipient 59 | ) external { 60 | IUniswapV3Pool(pool).swap(recipient, true, type(int256).max, sqrtPriceX96, abi.encode(msg.sender)); 61 | } 62 | 63 | function swapToHigherSqrtPrice( 64 | address pool, 65 | uint160 sqrtPriceX96, 66 | address recipient 67 | ) external { 68 | IUniswapV3Pool(pool).swap(recipient, false, type(int256).max, sqrtPriceX96, abi.encode(msg.sender)); 69 | } 70 | 71 | function uniswapV3SwapCallback( 72 | int256 amount0Delta, 73 | int256 amount1Delta, 74 | bytes calldata data 75 | ) external { 76 | address sender = abi.decode(data, (address)); 77 | 78 | if (amount0Delta > 0) { 79 | IERC20(IUniswapV3Pool(msg.sender).token0()).transferFrom(sender, msg.sender, uint256(amount0Delta)); 80 | } else if (amount1Delta > 0) { 81 | IERC20(IUniswapV3Pool(msg.sender).token1()).transferFrom(sender, msg.sender, uint256(amount1Delta)); 82 | } else { 83 | // if both are not gt 0, both must be 0. 84 | assert(amount0Delta == 0 && amount1Delta == 0); 85 | } 86 | } 87 | 88 | function mint( 89 | address pool, 90 | address recipient, 91 | int24 tickLower, 92 | int24 tickUpper, 93 | uint128 amount 94 | ) external { 95 | IUniswapV3Pool(pool).mint(recipient, tickLower, tickUpper, amount, abi.encode(msg.sender)); 96 | } 97 | 98 | function uniswapV3MintCallback( 99 | uint256 amount0Owed, 100 | uint256 amount1Owed, 101 | bytes calldata data 102 | ) external { 103 | address sender = abi.decode(data, (address)); 104 | 105 | if (amount0Owed > 0) 106 | IERC20(IUniswapV3Pool(msg.sender).token0()).transferFrom(sender, msg.sender, amount0Owed); 107 | if (amount1Owed > 0) 108 | IERC20(IUniswapV3Pool(msg.sender).token1()).transferFrom(sender, msg.sender, amount1Owed); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /contracts/vendor/ISwapRouterV3.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity >=0.7.5; 3 | pragma abicoder v2; 4 | 5 | import './IUniswapV3SwapCallback.sol'; 6 | 7 | /// @title Router token swapping functionality 8 | /// @notice Functions for swapping tokens via Uniswap V3 9 | interface ISwapRouterV3 is IUniswapV3SwapCallback { 10 | struct ExactInputSingleParams { 11 | address tokenIn; 12 | address tokenOut; 13 | uint24 fee; 14 | address recipient; 15 | uint256 deadline; 16 | uint256 amountIn; 17 | uint256 amountOutMinimum; 18 | uint160 sqrtPriceLimitX96; 19 | } 20 | 21 | /// @notice Swaps `amountIn` of one token for as much as possible of another token 22 | /// @param params The parameters necessary for the swap, encoded as `ExactInputSingleParams` in calldata 23 | /// @return amountOut The amount of the received token 24 | function exactInputSingle(ExactInputSingleParams calldata params) external payable returns (uint256 amountOut); 25 | 26 | struct ExactInputParams { 27 | bytes path; 28 | address recipient; 29 | uint256 deadline; 30 | uint256 amountIn; 31 | uint256 amountOutMinimum; 32 | } 33 | 34 | /// @notice Swaps `amountIn` of one token for as much as possible of another along the specified path 35 | /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactInputParams` in calldata 36 | /// @return amountOut The amount of the received token 37 | function exactInput(ExactInputParams calldata params) external payable returns (uint256 amountOut); 38 | 39 | struct ExactOutputSingleParams { 40 | address tokenIn; 41 | address tokenOut; 42 | uint24 fee; 43 | address recipient; 44 | uint256 deadline; 45 | uint256 amountOut; 46 | uint256 amountInMaximum; 47 | uint160 sqrtPriceLimitX96; 48 | } 49 | 50 | /// @notice Swaps as little as possible of one token for `amountOut` of another token 51 | /// @param params The parameters necessary for the swap, encoded as `ExactOutputSingleParams` in calldata 52 | /// @return amountIn The amount of the input token 53 | function exactOutputSingle(ExactOutputSingleParams calldata params) external payable returns (uint256 amountIn); 54 | 55 | struct ExactOutputParams { 56 | bytes path; 57 | address recipient; 58 | uint256 deadline; 59 | uint256 amountOut; 60 | uint256 amountInMaximum; 61 | } 62 | 63 | /// @notice Swaps as little as possible of one token for `amountOut` of another along the specified path (reversed) 64 | /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactOutputParams` in calldata 65 | /// @return amountIn The amount of the input token 66 | function exactOutput(ExactOutputParams calldata params) external payable returns (uint256 amountIn); 67 | } 68 | -------------------------------------------------------------------------------- /contracts/vendor/IUniswapV3SwapCallback.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity >=0.5.0; 3 | 4 | /// @title Callback for IUniswapV3PoolActions#swap 5 | /// @notice Any contract that calls IUniswapV3PoolActions#swap must implement this interface 6 | interface IUniswapV3SwapCallback { 7 | /// @notice Called to `msg.sender` after executing a swap via IUniswapV3Pool#swap. 8 | /// @dev In the implementation you must pay the pool tokens owed for the swap. 9 | /// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory. 10 | /// amount0Delta and amount1Delta can both be 0 if no tokens were swapped. 11 | /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by 12 | /// the end of the swap. If positive, the callback must send that amount of token0 to the pool. 13 | /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by 14 | /// the end of the swap. If positive, the callback must send that amount of token1 to the pool. 15 | /// @param data Any data passed through by the caller via the IUniswapV3PoolActions#swap call 16 | function uniswapV3SwapCallback( 17 | int256 amount0Delta, 18 | int256 amount1Delta, 19 | bytes calldata data 20 | ) external; 21 | } 22 | -------------------------------------------------------------------------------- /contracts/vendor/MerkleProof.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // OpenZeppelin Contracts (last updated v4.5.0) (utils/cryptography/MerkleProof.sol) 3 | 4 | pragma solidity ^0.8.0; 5 | 6 | /** 7 | * @dev These functions deal with verification of Merkle Trees proofs. 8 | * 9 | * The proofs can be generated using the JavaScript library 10 | * https://github.com/miguelmota/merkletreejs[merkletreejs]. 11 | * Note: the hashing algorithm should be keccak256 and pair sorting should be enabled. 12 | * 13 | * See `test/utils/cryptography/MerkleProof.test.js` for some examples. 14 | */ 15 | library MerkleProof { 16 | /** 17 | * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree 18 | * defined by `root`. For this, a `proof` must be provided, containing 19 | * sibling hashes on the branch from the leaf to the root of the tree. Each 20 | * pair of leaves and each pair of pre-images are assumed to be sorted. 21 | */ 22 | function verify( 23 | bytes32[] memory proof, 24 | bytes32 root, 25 | bytes32 leaf 26 | ) internal pure returns (bool) { 27 | return processProof(proof, leaf) == root; 28 | } 29 | 30 | /** 31 | * @dev Returns the rebuilt hash obtained by traversing a Merklee tree up 32 | * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt 33 | * hash matches the root of the tree. When processing the proof, the pairs 34 | * of leafs & pre-images are assumed to be sorted. 35 | * 36 | * _Available since v4.4._ 37 | */ 38 | function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) { 39 | bytes32 computedHash = leaf; 40 | for (uint256 i = 0; i < proof.length; i++) { 41 | bytes32 proofElement = proof[i]; 42 | if (computedHash <= proofElement) { 43 | // Hash(current computed hash + current element of the proof) 44 | computedHash = _efficientHash(computedHash, proofElement); 45 | } else { 46 | // Hash(current element of the proof + current computed hash) 47 | computedHash = _efficientHash(proofElement, computedHash); 48 | } 49 | } 50 | return computedHash; 51 | } 52 | 53 | function _efficientHash(bytes32 a, bytes32 b) private pure returns (bytes32 value) { 54 | assembly { 55 | mstore(0x00, a) 56 | mstore(0x20, b) 57 | value := keccak256(0x00, 0x40) 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /contracts/vendor/RPow.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | 3 | // From MakerDAO DSS 4 | 5 | // Copyright (C) 2018 Rain 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU Affero General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU Affero General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU Affero General Public License 18 | // along with this program. If not, see . 19 | 20 | pragma solidity ^0.8.0; 21 | 22 | library RPow { 23 | function rpow(uint x, uint n, uint base) internal pure returns (uint z) { 24 | assembly { 25 | switch x case 0 {switch n case 0 {z := base} default {z := 0}} 26 | default { 27 | switch mod(n, 2) case 0 { z := base } default { z := x } 28 | let half := div(base, 2) // for rounding. 29 | for { n := div(n, 2) } n { n := div(n,2) } { 30 | let xx := mul(x, x) 31 | if iszero(eq(div(xx, x), x)) { revert(0,0) } 32 | let xxRound := add(xx, half) 33 | if lt(xxRound, xx) { revert(0,0) } 34 | x := div(xxRound, base) 35 | if mod(n,2) { 36 | let zx := mul(z, x) 37 | if and(iszero(iszero(x)), iszero(eq(div(zx, x), z))) { revert(0,0) } 38 | let zxRound := add(zx, half) 39 | if lt(zxRound, zx) { revert(0,0) } 40 | z := div(zxRound, base) 41 | } 42 | } 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /docs/attacks.md: -------------------------------------------------------------------------------- 1 | This document is a non-comprehensive list of attacker models to consider during design. 2 | 3 | ## Creating eTokens 4 | 5 | * Creating eTokens with hostile token 6 | * eToken that uses another eToken as underlying 7 | 8 | ## Hostile Tokens 9 | 10 | * Change name or symbol 11 | * Change decimals 12 | * Arbitrarily change balance of the eToken 13 | * Sometimes legitimate tokens can charge fees on transfer (see compound's doTransferIn) 14 | * Some tokens are deflationary (or inflationary, ie aTokens) 15 | * Make methods like balanceOf() or transfer() fail 16 | * Could prevent liquidations 17 | * Make methods like balanceOf() consume excessive gas 18 | * Cause overflows in amounts 19 | * Could lead to failures calculating liquidity 20 | * Token with balance just under the limit, and then interest is accrued and it goes over 21 | * token SELFDESTRUCTS 22 | 23 | ## Gas 24 | 25 | * In callBalanceOf(), we proceed even when a call to a token fails due to out of gas condition. 26 | An attacker could carefully choose a gasLimit to cause this to return 0 for an honest token 27 | that in fact has a non-zero balance. 28 | 29 | ## Price oracles 30 | 31 | * Cause overflows in price calculations 32 | * Could lead to failures calculating liquidity 33 | * When liquidity is exhausted, attacker can manipulate the price arbitrarily 34 | Noah 26/03/2021: i agree with your first point! re. what happens once the entire order book is eaten up, you can freely continue in the "same direction" for 0 cost (other than gas), meaning you can manipulate the price for free up to the limit. is your worry that this could be used to oracle attack? 35 | moody 26/03/2021: you can also consider using the time weighted average liquidity to throw out prices when the liquidity is less than some deviation from the mean (this is a tricky problem though) 36 | * Spam orders at every tick to cause gas usage to liquidate too high 37 | * Noah thinks this isn't a problem: liquidity should congregate to pools with tick spacing proportional to volatility. ticks are in log space, so the more ticks are crossed, the more price impact you eat. we expect the median swap to cross ~0 - ~2 ticks. each tick crossing is like a ~40k gas overhead iirc 38 | 39 | 40 | ## Liquidations 41 | 42 | * Self-liquidation 43 | https://medium.com/@mcateer/compounds-self-liquidation-bug-829d6571c2df 44 | -------------------------------------------------------------------------------- /docs/audit.md: -------------------------------------------------------------------------------- 1 | ## Areas of concern 2 | 3 | * Proxy/module implementation is fairly novel, so would appreciate sanity checking that 4 | * Hostile tokens that cause liquidity checks to fail could allow an attacker to prevent their own liquidation: 5 | * callBalanceOf() should return 0 on any sort of failure and not abort tx 6 | * extreme prices on uniswap pools could cause integer overflows during liquidity computation 7 | * Deferred liquidity checks are how we're implementing flash loans, which is a bit different and could use checking 8 | * tests for this are in progress now 9 | 10 | ## Work in progress 11 | 12 | * Liquidations aren't yet well tested 13 | * Our production interest rate model is still being worked on, and is not yet committed 14 | * Fleshing out test-suite and improving coverage is our current primary focus 15 | -------------------------------------------------------------------------------- /docs/custom_environments.md: -------------------------------------------------------------------------------- 1 | ## custom environments 2 | For each blockchain node provider, e.g., alchemy, rivet or infura, we need to create a separate .env file names as .env., i.e., ```.env.alchemy``` with the following environment variables on a new line: 3 | 4 | ``` 5 | PRIVATE_KEY="c....." 6 | RPC_URL_KOVAN="https://eth-kovan.alchemyapi.io/v2/....." 7 | RPC_URL_ROPSTEN="https://eth-ropsten.alchemyapi.io/v2/...." 8 | RPC_URL_GOERLI="https://eth-goerli.alchemyapi.io/v2/....." 9 | ``` 10 | 11 | When running a hardhat command or task from the command line, to point to a specific environment file, we can do this using an environment variable called `NODE_ENV` that points to the environment name, with the `--network` flag that will tell the hardhat config which network url to use within the environment file e.g., alchemy as follows: 12 | 13 | NODE_ENV=alchemy npx hardhat --network ropsten testtoken:balanceOf USDC 0x71e48c397a37597D9813Ef1E11c60F4c5528E3de 14 | 15 | An error message specifying that the network does not exist is returned if the .env RPC URL variable for the network is not set. 16 | -------------------------------------------------------------------------------- /docs/invariants.md: -------------------------------------------------------------------------------- 1 | * If a user has a non-zero borrow owed: 2 | * they must be entered into market 3 | * must have a non-zero interest accumulator 4 | * If totalBorrows > 0, an asset must have a non-zero interest accumulator 5 | * reentrancyLock must always be restored to REENTRANCYLOCK__UNLOCKED 6 | * A user cannot have duplicate entries in their entered market list 7 | 8 | * on eTokens: sumAll(balanceOf) + reserveBalance == totalBalance 9 | * sumAll(balanceOfUnderlying)) + reserveBalanceUnderlying <= totalSupplyUnderlying <= balanceOf(euler) + totalBorrows 10 | (When adding up individual balances and the reserves, fractions of underlying can get rounded down. Similarly, the totalSupplyUnderlying can be rounded down by at most 1 token unit.) 11 | 12 | * deposit should only result in an increase of eToken balance, and should affect no other asset 13 | * withdraw should only result in a decrease of eToken balance, and should affect no other asset 14 | * borrow should only result in an increase of dToken balance, and should affect no other asset 15 | * repay should only result in a decrease of dToken balance, and should affect no other asset 16 | 17 | * No protocol action should be able to result in an account with risk adjusted liability > risk adjusted assets (checkLiquidity failing) 18 | * If an asset is borrow isolated, any user borrowing it must not be borrowing any other asset 19 | * No proxy address (eToken/dToken/etc) can be activated as a market 20 | * No token with collateral factor of 0 can contribute to your risk-adjusted asset value 21 | * Passing in a sub-account id > 255 should fail, everywhere 22 | -------------------------------------------------------------------------------- /docs/limits.md: -------------------------------------------------------------------------------- 1 | ## amounts 2 | 3 | `uint112` 4 | 5 | * Maximum sane amount (result of balanceOf) for external tokens 6 | * Uniswap2 limits token amounts to this 7 | * Spec: For an 18 decimal token, more than a million billion tokens (1e15) 8 | 9 | ## small amounts 10 | 11 | `uint96` 12 | 13 | * For holding amounts that we don't expect to get quite as large, in particular reserve balances 14 | * Can pack together with an address in a single slot 15 | * Spec: For an 18 decimal token, more than a billion tokens (1e9) 16 | 17 | ## debt amounts 18 | 19 | `uint144` 20 | 21 | * Maximum sane amount for debts 22 | * Packs together with an amount in a single storage slot 23 | * Spec: Should hold the maximum possible amount (uint112) but scaled by another 9 decimal places (for the internal debt precision) 24 | * Actual: 2e16 25 | 26 | ## prices 27 | 28 | A price is a fraction that represents how many reference asset units (WETH) you will get for each unit of an underlying. 29 | 30 | *After* normalising the underlying asset's decimals to 18, prices fall within the range `1e-18` to `1e18`. 31 | 32 | * For the purpose of liquidity calculation, prices below `1e-18` round up to `1e-18`, and prices above `1e18` round down to `1e18`. 33 | * Due to precision loss, the practical range of prices on Euler is around `1e-15` through `1e15`. Assets with prices outside this range should be used with care (and certainly should have a zero collateral factor) 34 | * Because price fractions are stored scaled by `1e18`, the internal representation of prices range from `1e0` to `1e36` 35 | * To avoid overflows during liquidity calculations, the maximum supported price (in internal representation) times the maximum supported amount fits within a uint256: `2^112 * 1e36 ~= 5.2e69 < 1e77` 36 | 37 | ## interestRate 38 | 39 | `int96` 40 | 41 | * "Second Percent Yield" 42 | * Fraction scaled by 1e27 43 | * Example: `10% APR = 1e27 * 0.1 / (86400*365) = 1e27 * 0.000000003170979198376458650 = 3170979198376458650` 44 | * Spec: 1 billion % APR, positive or negative 45 | 46 | ## interestAccumulator 47 | 48 | `uint256` 49 | 50 | * Starts at 1e27, multiplied by (1e27 + interestRate) every second 51 | * Spec: 100% APR for 100 years 52 | -> 2^256 53 | ~= 1.1579208923e+77 54 | -> 10^27 * (1 + (100/100 / (86400*365)))^(86400*365*100) 55 | ~= 2.6881128798e+70 56 | 57 | ## moduleId 58 | 59 | `uint32` 60 | 61 | * One per module, so this is way more than needed 62 | * Divided into 3 sections 63 | * <500_000: Public single-proxy 64 | * >=500_000 and <1_000_000: Public multi-proxy 65 | * >=1_000_000: Internal 66 | * Spec: A dozen or so modules, with room to grow in all sections 67 | 68 | ## collateralFactor/borrowFactor 69 | 70 | `uint32` 71 | 72 | * Fraction between 0 and 1, scaled by 2^32 - 1 73 | * Spec: At least 3 decimal places (overkill) 74 | -------------------------------------------------------------------------------- /docs/liquidations.md: -------------------------------------------------------------------------------- 1 | ## Liquidations 2 | 3 | * One potential issue: If an account has a health score so low that a partial liquidation can actually reduce the user's health score, then a liquidator might deliberately do a small partial liquidation. Because this reduces the user's health score, it could actually increase the discount the liquidator receives on subsequent liquidations. The liquidator could do this in a smart contract and iteratively increase the discount higher and higher. 4 | * Possible mitigation: After the first liquidation, set a max discount that will be in effect for the next N seconds. Even N=1 would be sufficient to force a liquidator to wait until the subsequent block, which would give competitors a chance to perform the liquidations also, which would disincentivise this attack. 5 | -------------------------------------------------------------------------------- /docs/self-collateralisation.md: -------------------------------------------------------------------------------- 1 | # Max Self-Collateralised Amounts 2 | 3 | In order to determine how much an asset can be self-collateralised, we consider what state the account would be in if a `burn(MAX_UINT)` was performed on the eToken of the asset to be self-collateralised. This has the effect that at least one of the user's asset or liability amounts of this asset will be 0. 4 | 5 | Given this "base" state, we can determine the maximum value for `mint()` that would succeed (ie, not cause a collateral violation). This value, called the "max self collateral" amount, is the value from the base state, not the user's current state. In order to find out how much additional can be minted from this amount, the minimum of the asset and liability amounts should be subtracted. 6 | 7 | ## Derivation 8 | 9 | First of all, consider an account with "other collateral" (`OC`). This represents the risk-adjusted collateral value in assets *other than* the asset to be minted, normalised to the reference asset (ETH). For now, assume the account has no assets or liabilities in the self-collateralised asset. 10 | 11 | All of the value of a mint is self-collateralised, except for `1-SCF`, where `SCF` is the self-collateral factor (`0.95`). This extra amount must be adjusted up by the asset's borrow factor (`BF`). Let `X` be the maximum mint value. The following equation relates the maximum mint amount (`X`) to `OC`: 12 | 13 | OC = X * (1 - SCF) / BF 14 | 15 | Solving for X: 16 | 17 | X = OC * BF / (1 - SCF) 18 | 19 | Now we will introduce the case where a user has either or both assets and liabilities in the self-collateralised asset, call these `SA` and `SL` respectively. Note that these are *not* risk-adjusted, but *are* normalised to the reference asset (ETH). 20 | 21 | If after the burn there are outstanding liabilities (`SL > SA`), then this outstanding amount can be represented as `Math.max(0, SL - SA)`. This amount cannot be self-collateralised (because post-burn the self-collateralised assets would be 0). That means that this full amount must be adjusted up by the asset's borrow factor, and the available `OC` reduced by this amount. Plugging this into the previous equation gives: 22 | 23 | X = (OC - Math.max(0, SL - SA)/BF) * BF / (1 - SCF) 24 | 25 | On the other hand, suppose that after the burn there are outstanding assets (`SA > SL`). This extra amount, represented as `Math.max(0, SA - SL)`, can be used to increase the maximum mint value according to the self-collateral factor. Because this additional asset will be itself used as self-collateral, the maximum amount of the extra mint that can be supported must subtract the collateral amount, so `1` must be subtracted from the multipler. For example, instead of multiplying by 20, we would multiply by 19 to make this extra amount fully self-collateralised. This gives: 26 | 27 | Math.max(0, SA - SL) * (1/(1 - SCF) - 1) 28 | 29 | Adding the above onto the maximum mint amount equation gives our final function: 30 | 31 | X = (OC - Math.max(0, SL - SA)/BF) * BF / (1 - SCF) + Math.max(0, SA - SL) * (1/(1 - SCF) - 1) 32 | 33 | Note: The above logic depends on the fact that the account has no other liabilities other than (potentially) an existing liability in the self-collateralised asset. We can make this assumption because self-collateralised borrows are always isolated. 34 | 35 | ## Usage 36 | 37 | As mentioned, the value for `X` is the maximum amount that an account can mint after it performs a `burn(MAX_UINT)`. In order to display the remaining amount that is available to mint, we must subtract the amount that this virtual burn would eliminate from `X`: 38 | 39 | availToMint = X - Math.min(SA, SL) 40 | 41 | In order to compute the account's current multiplier, we first need to know the maximum possible multiplier. This is a direct consequence of the SCF value. As described above, the SCF determines how much of a total position is eligible for self-collateralisation. This means that the starting value must be subtracted off when determining how much additional can be minted. In the case of a multiplier, the starting amount is `1` so this must be subtracted off: 42 | 43 | maxMultiplier = 1/(1 - SCF) - 1 44 | = 1/0.05 - 1 45 | = 20 - 1 46 | = 19 47 | 48 | So depending on the state of an account, its multiplier will lie between 0 and 19 (or technically higher, if it is in collateral violation). The current multiplier can be found by multiplying `maxMultiplier` by how much the current virtual burn amount is using out of the total `X` mint amount: 49 | 50 | currMultiplier = maxMultiplier * Math.min(SA, SL) / X 51 | -------------------------------------------------------------------------------- /docs/sub-accounts.md: -------------------------------------------------------------------------------- 1 | ## Problem 2 | 3 | We don't want to require users to create separate accounts for separate loans. 4 | 5 | This is especially because we don't allow multiple assets to be borrowed simultaneously by default, but also to give users control over how their collateral is allocated to each loan. 6 | 7 | * Expensive to move funds around 8 | * Pain to manage multiple accounts with metamask 9 | * Even worse with hardware wallet 10 | * dApp doesn't know accounts are linked 11 | 12 | 13 | ## Solution 14 | 15 | Allow an address to control "sub-accounts" that are totally isolated with respect to collateral/borrows 16 | 17 | It is desirable that the user's normal address works as a "primary" account, so that integrations don't need to be aware of sub-accounts. For example, transferring ETokens with metamask. 18 | 19 | The following methods accept a subAccountId as the first parameter: 20 | 21 | * eToken.deposit 22 | * eToken.withdraw 23 | * eToken.approveSubAccount 24 | * dToken.borrow 25 | * dToken.repay 26 | * dToken.approveSubAccount 27 | * markets.enterMarket 28 | * markets.exitMarket 29 | 30 | 31 | The `transferFrom` methods on eTokens/dTokens can be used to move collateral and debt between sub-accounts 32 | 33 | 34 | ## Sub-account format 35 | 36 | There is a limit of 256 sub-accounts per primary account. 37 | 38 | In order to compute the sub-account address from the primary, xor the primary with the sub-account ID: 39 | 40 | subAccountAddress = primaryAddress ^ subAccountId 41 | 42 | This scheme has the following nice properties: 43 | 44 | * When using the sub-account ID of 0, the account address is the same as the primary account. 45 | * Otherwise, the first 19 bytes (38 hex digits) will be the same, but the last byte can differ. 46 | * This means a simple lexical ordering of accounts in a DB will allow easy selection of all sub-accounts related to a primary. 47 | -------------------------------------------------------------------------------- /docs/tasks.md: -------------------------------------------------------------------------------- 1 | # Tasks cheat-sheet 2 | 3 | 4 | ## Create and activate a test token 5 | 6 | npx hardhat --network ropsten testtoken:deploy 'USD Coin' USDC --decimals 6 7 | -> put address into test/lib/token-setups/ropsten.js 8 | 9 | npx hardhat --network ropsten uniswap:create-pool USDC ref 10 | 11 | npx hardhat --network ropsten euler markets.activateMarket USDC 12 | 13 | 14 | 15 | 16 | ## Read full view of token 17 | 18 | npx hardhat --network ropsten view USDC 19 | 20 | 21 | ## Get underlying token balance 22 | 23 | npx hardhat --network ropsten euler tokens.USDC.balanceOf me 24 | 25 | 26 | ## Mint test tokens 27 | 28 | npx hardhat --network ropsten testtoken:mint USDC me 1000000 29 | 30 | 31 | ## Check Euler's allowance on token 32 | 33 | npx hardhat --network ropsten euler tokens.USDC.allowance me euler 34 | 35 | 36 | ## Check your E-token balance 37 | 38 | npx hardhat --network ropsten euler eTokens.eUSDC.balanceOf me 39 | 40 | 41 | ## Approve and deposit into an eToken 42 | 43 | npx hardhat --network ropsten euler tokens.USDC.approve euler max 44 | 45 | npx hardhat --network ropsten euler eTokens.eUSDC.deposit 0 1e18 46 | 47 | 48 | ## Transfer 0.001 eUSDC to burn addr 49 | 50 | npx hardhat --network ropsten euler eTokens.eUSDC.transferFrom me 0x0000000000000000000000000000000000000001 1e15 51 | 52 | 53 | 54 | ## Get entered markets 55 | 56 | npx hardhat --network ropsten euler markets.getEnteredMarkets me 57 | 58 | 59 | 60 | ## Enter market 61 | 62 | npx hardhat --network ropsten euler markets.enterMarket 0 token:USDC 63 | 64 | 65 | 66 | ## Query an asset's config 67 | 68 | npx hardhat --network ropsten euler markets.underlyingToAssetConfig token:USDC 69 | 70 | 71 | ## Update an asset's config 72 | 73 | npx hardhat --network ropsten gov:setAssetConfig USDC --cfactor .9 --bfactor .9 --isolated false 74 | 75 | 76 | ## Update an asset's pricing config 77 | 78 | npx hardhat --network ropsten gov:setPricingConfig USDC 2 500 79 | 80 | 81 | ## Query an asset's price, as seen by Euler 82 | 83 | npx hardhat --network ropsten euler --callstatic exec.getPriceFull token:USDC 84 | 85 | 86 | ## Read TWAP directly from uniswap pool (debugging only) 87 | 88 | npx hardhat --network ropsten uniswap:read-twap USDC ref 3000 1800 89 | 90 | 91 | 92 | ## Deploy a non-module contract 93 | 94 | npx hardhat --network ropsten module:deploy EulerGeneralView 95 | -> update address in addresses/euler-addresses-ropsten.json 96 | 97 | 98 | ## Upgrade a module 99 | 100 | npx hardhat --network ropsten module:deploy RiskManager 101 | -> update address in addresses/euler-addresses-ropsten.json 102 | 103 | npx hardhat --network ropsten module:install [address printed in prev step] 104 | 105 | 106 | 107 | ## Activate PToken 108 | 109 | npx hardhat --network ropsten euler markets.activatePToken token:USDC 110 | 111 | 112 | 113 | ## Update EulDistributor root 114 | 115 | npx hardhat --network ropsten euler eulDistributor.updateRoot 0x4b594c5fe1180ca4b6d25aa06798b656c8eaf54b368776f03ff79426fe76b88a 116 | 117 | 118 | 119 | ## Increase uniswap observation cardinality 120 | 121 | npx hardhat --network mainnet uniswap:increase-observation-cardinality 0x3b484b82567a09e2588a13d54d032153f0c0aee0 ref 10000 10 122 | 123 | * `ref` is "reference asset", ie WETH 124 | * 10000 corresponds to 1% fee pool 125 | * New minimum cardinality is 10 126 | -------------------------------------------------------------------------------- /docs/verification.md: -------------------------------------------------------------------------------- 1 | # Contract Verification 2 | 3 | ## Etherscan verification 4 | 5 | After deploying a module, it can be verified on etherscan like this: 6 | 7 | NODE_ENV=mainnet npx hardhat verification:get-build contracts/modules/RiskManager.sol verif.json 8 | 9 | The `verif.json` file created will be a "standard json" format that can be submitted to etherscan. 10 | 11 | ## Diffing contracts 12 | 13 | Given two contract addresses that have both been verified on etherscan, the differences in their code can be examined with the `verification:diff-contracts` command. 14 | 15 | First, the `ETHERSCAN` variable needs to be set in your env file, ie `.env.mainnet`. This should be an API key for etherscan, which is used to download the verified code. 16 | 17 | Then run the following command, using the contract addresses you are interested in comparing: 18 | 19 | NODE_ENV=mainnet npx hardhat verification:diff-contracts OLD_ADDR NEW_ADDR 20 | 21 | ## Diffing a contracts against the git repo 22 | 23 | In case you would like to inspect the differences between a verified contract and the current state of the git repo, you can use the `verification:diff-contract-from-repo` command, replacing the contract path and address: 24 | 25 | NODE_ENV=mainnet npx hardhat verification:diff-contract-from-repo contracts/modules/RiskManager.sol ADDR 26 | -------------------------------------------------------------------------------- /hardhat.config.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | require("@nomiclabs/hardhat-waffle"); 3 | require("@nomiclabs/hardhat-etherscan"); 4 | require("hardhat-contract-sizer"); 5 | require('hardhat-gas-reporter'); 6 | require("solidity-coverage"); 7 | 8 | 9 | // Load tasks 10 | 11 | const files = fs.readdirSync('./tasks'); 12 | 13 | for (let file of files) { 14 | if (!file.endsWith('.js')) continue; 15 | require(`./tasks/${file}`); 16 | } 17 | 18 | 19 | // Config 20 | 21 | module.exports = { 22 | networks: { 23 | hardhat: { 24 | hardfork: 'arrowGlacier', 25 | chainId: 1, 26 | }, 27 | localhost: { 28 | chainId: 1, 29 | url: "http://127.0.0.1:8545", 30 | timeout: 5 * 60 * 1000, 31 | }, 32 | }, 33 | 34 | solidity: { 35 | compilers: [ 36 | { 37 | version: "0.8.10", 38 | settings: { 39 | optimizer: { 40 | enabled: true, 41 | runs: 1000000, 42 | }, 43 | outputSelection: { 44 | "contracts/Storage.sol": { 45 | "*": [ 46 | "storageLayout", 47 | ], 48 | }, 49 | }, 50 | }, 51 | }, 52 | ], 53 | }, 54 | 55 | gasReporter: { 56 | enabled: !!process.env.REPORT_GAS, 57 | }, 58 | 59 | contractSizer: { 60 | //runOnCompile: true, 61 | }, 62 | 63 | mocha: { 64 | timeout: 100000 65 | } 66 | }; 67 | 68 | 69 | if (process.env.NODE_ENV) { 70 | let path = `.env.${process.env.NODE_ENV}`; 71 | if (!fs.existsSync(path)) throw(`unable to open env file: ${path}`); 72 | require("dotenv").config({ path, }); 73 | } else if (fs.existsSync('./.env')) { 74 | require("dotenv").config(); 75 | } 76 | 77 | for (let k in process.env) { 78 | if (k.startsWith("RPC_URL_")) { 79 | let networkName = k.slice(8).toLowerCase(); 80 | 81 | module.exports.networks = { 82 | ...module.exports.networks, 83 | [networkName]: { 84 | url: `${process.env[k]}`, 85 | accounts: [`0x${process.env.PRIVATE_KEY}`], 86 | } 87 | } 88 | } 89 | 90 | if (k === "ETHERSCAN_API_KEY") { 91 | module.exports.etherscan = { 92 | apiKey: { 93 | // ethereum smart contract verification key 94 | mainnet: process.env[k], 95 | goerli: process.env[k] 96 | } 97 | } 98 | } 99 | 100 | if (k === "POLYGONSCAN_API_KEY") { 101 | module.exports.etherscan = { 102 | apiKey: { 103 | // polygon smart contract verification key 104 | polygon: process.env[k], 105 | polygonMumbai: process.env[k] 106 | } 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "euler-contracts", 3 | "license": "UNLICENSED", 4 | "version": "1.0.0", 5 | "scripts": { 6 | "node": "npx hardhat node", 7 | "node:test": "npx hardhat node --hostname 0.0.0.0", 8 | "node:ganache": "./node_modules/.bin/ganache-cli -m 'test test test test test test test test test test test junk'", 9 | "deploy": "npx hardhat run --network localhost scripts/dev-setup.js", 10 | "pm2": "pm2 start npm --name \"blockchain\" -- run node:test", 11 | "push": "ssh-add ~/.ssh/github_id_rsa && git push", 12 | "compile": "npx hardhat compile" 13 | }, 14 | "devDependencies": { 15 | "@nomiclabs/hardhat-ethers": "^2.0.0", 16 | "@nomiclabs/hardhat-etherscan": "^3.1.2", 17 | "@nomiclabs/hardhat-waffle": "^2.0.0", 18 | "@uniswap/sdk-core": "^3.0.1", 19 | "@uniswap/v3-sdk": "^3.4.1", 20 | "chai": "^4.2.0", 21 | "cross-fetch": "^3.1.4", 22 | "ethereum-block-by-date": "^1.4.2", 23 | "ethereum-waffle": "^3.3.0", 24 | "ethers": "^5.7.0", 25 | "ganache-cli": "^6.12.1", 26 | "hardhat": "^2.10.2", 27 | "hardhat-contract-sizer": "^2.0.3", 28 | "hardhat-gas-reporter": "^1.0.6", 29 | "solidity-coverage": "^0.7.16" 30 | }, 31 | "dependencies": { 32 | "dotenv": "^8.2.0", 33 | "hardhat-spdx-license-identifier": "^2.0.3", 34 | "seedrandom": "^3.0.5" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /scripts/calculate-irm-linear-kink.js: -------------------------------------------------------------------------------- 1 | const ethers = require('ethers'); 2 | 3 | if (process.argv.length !== 6) { 4 | console.log("Usage: calculate-irm-linear-kink.js "); 5 | process.exit(1); 6 | } 7 | 8 | let origBaseIr = process.argv[2]; 9 | let origKinkIr = process.argv[3]; 10 | let origMaxIr = process.argv[4]; 11 | let origKink = process.argv[5]; 12 | 13 | let baseIr = parseIr(origBaseIr); 14 | let kinkIr = parseIr(origKinkIr); 15 | let maxIr = parseIr(origMaxIr); 16 | let kink = parseFloat(origKink) / 100; 17 | 18 | if (kink < 0 || kink > 1) throw(`bad kink`); 19 | if (baseIr.gt(kinkIr)) throw(`baseIr > kinkIr`); 20 | if (kinkIr.gt(maxIr)) throw(`kinkIr > maxIr`); 21 | 22 | 23 | kink = Math.floor(kink * 2**32); 24 | 25 | let slope1 = kinkIr.sub(baseIr).div(kink); 26 | let slope2 = maxIr.sub(kinkIr).div(2**32 - kink); 27 | 28 | console.log(` // Base=${origBaseIr}% APY, Kink(${origKink}%)=${origKinkIr}% APY Max=${origMaxIr}% APY`); 29 | console.log(` ${baseIr.toString()}, ${slope1.toString()}, ${slope2.toString()}, ${kink}`); 30 | 31 | 32 | 33 | 34 | function parseIr(p) { 35 | p = parseFloat(p) / 100; 36 | p = Math.log(1 + p); 37 | return ethers.BigNumber.from(Math.floor(p * 1e9)) 38 | .mul(ethers.BigNumber.from(10).pow(27 - 9)) 39 | .div(365.2425 * 86400); 40 | } 41 | -------------------------------------------------------------------------------- /scripts/dev-setup.js: -------------------------------------------------------------------------------- 1 | const hre = require("hardhat"); 2 | const ethers = hre.ethers; 3 | const fs = require("fs"); 4 | 5 | const eTestLib = require("../test/lib/eTestLib"); 6 | 7 | async function main() { 8 | const ctx = await eTestLib.deployContracts(ethers.provider, await ethers.getSigners(), 'testing'); 9 | 10 | eTestLib.writeAddressManifestToFile(ctx, "./euler-addresses.json"); 11 | 12 | // Supply tokens to test account 13 | 14 | for (let token of Object.keys(ctx.contracts.tokens)) { 15 | await ctx.contracts.tokens[token].mint(ctx.wallet.address, ethers.utils.parseEther("10000")); 16 | } 17 | 18 | for (let addr of eTestLib.defaultTestAccounts) { 19 | await ctx.contracts.tokens.TST.mint(addr, ethers.utils.parseEther("1000")); 20 | await ctx.contracts.tokens.TST2.mint(addr, ethers.utils.parseEther("1000")); 21 | } 22 | 23 | // Setting prices 24 | 25 | await ctx.updateUniswapPrice("TST/WETH", "0.005882"); 26 | await ctx.updateUniswapPrice("TST2/WETH", "0.000047411"); 27 | await ctx.updateUniswapPrice("TST3/WETH", "6.9145811"); 28 | await ctx.updateUniswapPrice("UTST/WETH", "0.019244"); 29 | 30 | // Fast forward time so prices become active 31 | 32 | await ctx.increaseTime(31 * 60); 33 | } 34 | 35 | main(); 36 | -------------------------------------------------------------------------------- /scripts/mint-tokens.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # example usage from project root folder - ./scripts/mint-tokens.sh alchemy 0x6dFa0D799d35DE1924b1EF27cA9ba57FC24a7458 4 | # example usage without script NODE_ENV=alchemy npx hardhat --network ropsten testtoken:mint BAT 0x71e48c397a37597D9813Ef1E11c60F4c5528E3de 1000 5 | 6 | 7 | NODE_ENV=$1 npx hardhat --network ropsten testtoken:mint DAI $2 1000 8 | sleep 2m 9 | NODE_ENV=$1 npx hardhat --network ropsten testtoken:mint USDC $2 1000 10 | sleep 2m 11 | NODE_ENV=$1 npx hardhat --network ropsten testtoken:mint USDT $2 1000 12 | sleep 2m 13 | NODE_ENV=$1 npx hardhat --network ropsten testtoken:mint WBTC $2 1000 14 | sleep 2m 15 | NODE_ENV=$1 npx hardhat --network ropsten testtoken:mint UNI $2 1000 16 | sleep 2m 17 | NODE_ENV=$1 npx hardhat --network ropsten testtoken:mint COMP $2 1000 18 | sleep 2m 19 | NODE_ENV=$1 npx hardhat --network ropsten testtoken:mint REP $2 1000 20 | sleep 2m 21 | NODE_ENV=$1 npx hardhat --network ropsten testtoken:mint BZRX $2 1000 22 | sleep 2m 23 | NODE_ENV=$1 npx hardhat --network ropsten testtoken:mint DOUGH $2 1000 24 | sleep 2m 25 | NODE_ENV=$1 npx hardhat --network ropsten testtoken:mint CRV $2 1000 26 | sleep 2m 27 | : <<'END' 28 | # cannot mint link in secure mode 29 | NODE_ENV=$1 npx hardhat --network ropsten testtoken:mint LINK $2 1000 30 | sleep 2m 31 | END 32 | NODE_ENV=$1 npx hardhat --network ropsten testtoken:mint NORD $2 1000 33 | sleep 2m 34 | NODE_ENV=$1 npx hardhat --network ropsten testtoken:mint PNK $2 1000 35 | sleep 2m 36 | NODE_ENV=$1 npx hardhat --network ropsten testtoken:mint NFY $2 1000 37 | sleep 2m 38 | NODE_ENV=$1 npx hardhat --network ropsten testtoken:mint AVGC $2 1000 39 | sleep 2m 40 | NODE_ENV=$1 npx hardhat --network ropsten testtoken:mint 127760 $2 1000 41 | sleep 2m 42 | # NEW TOKENS 43 | NODE_ENV=$1 npx hardhat --network ropsten testtoken:mint BAT $2 1000 44 | sleep 2m 45 | NODE_ENV=$1 npx hardhat --network ropsten testtoken:mint MKR $2 1000 46 | sleep 2m 47 | NODE_ENV=$1 npx hardhat --network ropsten testtoken:mint renBTC $2 1000 48 | sleep 2m 49 | NODE_ENV=$1 npx hardhat --network ropsten testtoken:mint LUSD $2 1000 50 | sleep 2m 51 | NODE_ENV=$1 npx hardhat --network ropsten testtoken:mint MANA $2 1000 52 | sleep 2m 53 | NODE_ENV=$1 npx hardhat --network ropsten testtoken:mint CELR $2 1000 54 | sleep 2m 55 | NODE_ENV=$1 npx hardhat --network ropsten testtoken:mint CVX $2 1000 56 | sleep 2m 57 | NODE_ENV=$1 npx hardhat --network ropsten testtoken:mint renDOGE $2 1000 58 | sleep 2m 59 | NODE_ENV=$1 npx hardhat --network ropsten testtoken:mint AAVE $2 1000 60 | sleep 2m -------------------------------------------------------------------------------- /scripts/prod-setup.js: -------------------------------------------------------------------------------- 1 | const hre = require("hardhat"); 2 | const ethers = hre.ethers; 3 | const fs = require("fs"); 4 | 5 | const eTestLib = require("../test/lib/eTestLib"); 6 | 7 | async function main() { 8 | let networkName = process.env.NETWORK_NAME; 9 | let verify = process.env.VERIFY_CONTRACTS; 10 | 11 | const ctx = await eTestLib.deployContracts(ethers.provider, await ethers.getSigners(), networkName, verify); 12 | 13 | eTestLib.writeAddressManifestToFile(ctx, `./euler-addresses-${networkName}.json`); 14 | } 15 | 16 | main(); 17 | -------------------------------------------------------------------------------- /scripts/set-gov-admin.js: -------------------------------------------------------------------------------- 1 | const hre = require("hardhat"); 2 | const ethers = hre.ethers; 3 | const fs = require("fs"); 4 | 5 | const eTestLib = require("../test/lib/eTestLib"); 6 | 7 | 8 | // Usage: NEWGOVADDR=0x... npx hardhat run --network localhost scripts/set-gov-admin.js 9 | 10 | async function main() { 11 | const ctx = await eTestLib.getScriptCtx('staging'); 12 | 13 | // Default ctx.wallet is the installAdmin 14 | 15 | ctx.contracts.installer.setGovernorAdmin(process.env.NEWGOVADDR); 16 | } 17 | 18 | main(); 19 | -------------------------------------------------------------------------------- /scripts/staging-setup.js: -------------------------------------------------------------------------------- 1 | const hre = require("hardhat"); 2 | const ethers = hre.ethers; 3 | const fs = require("fs"); 4 | 5 | const eTestLib = require("../test/lib/eTestLib"); 6 | 7 | async function main() { 8 | const ctx = await eTestLib.deployContracts(ethers.provider, await ethers.getSigners(), 'staging'); 9 | 10 | eTestLib.writeAddressManifestToFile(ctx, "./euler-addresses.json"); 11 | 12 | // Supply tokens to test accounts 13 | 14 | for (let token of Object.keys(ctx.contracts.tokens)) { 15 | for (let addr of eTestLib.defaultTestAccounts) { 16 | await ctx.contracts.tokens[token].mint(addr, ethers.utils.parseEther("10000")); 17 | } 18 | } 19 | } 20 | 21 | main(); 22 | -------------------------------------------------------------------------------- /scripts/staging-sim.js: -------------------------------------------------------------------------------- 1 | const hre = require("hardhat"); 2 | const ethers = hre.ethers; 3 | const fs = require("fs"); 4 | 5 | const eTestLib = require("../test/lib/eTestLib"); 6 | 7 | 8 | let defaultMinInterval = 30 * 60; 9 | let defaultTradePeriod = 30; // uniswap trade will happen every N seconds, on average 10 | let defaultPriceQueuePeriod = 60; // opentwap price queueing will happen every N seconds, on average 11 | 12 | let tokens = [ 13 | { 14 | sym: 'DAI', 15 | mid: 0.000562089, 16 | deviation: 0.01, 17 | }, 18 | { 19 | sym: 'USDC', 20 | mid: 0.000555855, 21 | deviation: 0.001, 22 | }, 23 | { 24 | sym: 'BAT', 25 | mid: 0.000472044, 26 | }, 27 | { 28 | sym: 'LINK', 29 | mid: 0.016419, 30 | }, 31 | { 32 | sym: 'UNI', 33 | mid: 0.0172596, 34 | }, 35 | { 36 | sym: 'YFI', 37 | mid: 20.3573, 38 | }, 39 | { 40 | sym: 'COMP', 41 | mid: 0.252798, 42 | }, 43 | ]; 44 | 45 | 46 | 47 | 48 | async function main() { 49 | const ctx = await eTestLib.getScriptCtx('staging'); 50 | 51 | let updatePrices = async () => { 52 | for (let token of tokens) { 53 | if (Math.random() < 1 / (token.tradePeriod || defaultTradePeriod)) { 54 | let newPrice = randPrice(token.mid || 0.001, token.deviation || .1); 55 | console.log(`Uniswap trade: ${token.sym}/WETH => ${newPrice}`) 56 | await ctx.updateUniswapPrice(`${token.sym}/WETH`, newPrice); 57 | } 58 | 59 | if (Math.random() < 1 / (token.priceQueuePeriod || defaultPriceQueuePeriod)) { 60 | let minInterval = token.minInterval || defaultMinInterval; 61 | console.log(`Queueing price: ${token.sym}/WETH minInterval=${minInterval}`) 62 | await ctx.queuePriceUpdate(`${token.sym}/WETH`, minInterval); 63 | } 64 | } 65 | }; 66 | 67 | let updateTime = async () => { 68 | await ctx.mineEmptyBlock(); 69 | 70 | let currBlockTime = (await ctx.provider.getBlock()).timestamp; 71 | let now = getCurrTime(); 72 | 73 | let behind = now - currBlockTime; 74 | if (behind <= 0) return; 75 | 76 | console.log(`Time: Jumping ${behind}s [${now} / ${currBlockTime}]`); 77 | await ctx.increaseTime(behind); 78 | }; 79 | 80 | 81 | setInterval(updatePrices, 1000); 82 | setInterval(updateTime, 13000); 83 | } 84 | 85 | main(); 86 | 87 | 88 | 89 | /////////////// 90 | 91 | function randPrice(mid, deviation) { 92 | let scale = 1 + (Math.random() * deviation * 2) - deviation; 93 | let price = mid * scale; 94 | price = '' + price; 95 | let [pre, post] = price.split('.'); 96 | return pre + '.' + post.substr(0,18); 97 | } 98 | 99 | function getCurrTime() { 100 | return Math.floor((new Date()).getTime() / 1000); 101 | } 102 | -------------------------------------------------------------------------------- /scripts/templates/IEuler.sol.tt: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.5.0; 3 | pragma abicoder v2; 4 | 5 | [% FOREACH contract IN contracts %] 6 | [% FOREACH item IN contract.preItems -%] 7 | [% comment(item.natspec.raw) %] 8 | [% item.def %] 9 | 10 | [% END -%] 11 | [%- comment(contract.natspec.raw) IF contract.natspec %] 12 | interface [% contract.name %] { 13 | [% FOREACH item IN contract.contractItems -%] 14 | [% indent(comment(item.natspec.raw)) %] 15 | [% indent(item.def) %][% "\n" IF !loop.last %] 16 | [% END -%] 17 | }[% "\n\n" IF !loop.last %] 18 | [%- END %] 19 | 20 | [% FOREACH network IN networks %] 21 | library EulerAddrs[% network.name %] { 22 | IEuler public constant euler = IEuler([% network.addrs.euler %]); 23 | IEulerMarkets public constant markets = IEulerMarkets([% network.addrs.markets %]); 24 | IEulerLiquidation public constant liquidation = IEulerLiquidation([% network.addrs.liquidation %]); 25 | IEulerExec public constant exec = IEulerExec([% network.addrs.exec %]); 26 | [%- IF network.addrs.swap %] 27 | IEulerSwap public constant swap = IEulerSwap([% network.addrs.swap %]); 28 | [%- ELSE %] 29 | // swap module not available on [% network.name %] 30 | [%- END %] 31 | } 32 | [% END -%] 33 | -------------------------------------------------------------------------------- /scripts/templates/contract-reference.md.tt: -------------------------------------------------------------------------------- 1 | # Euler Contract Interfaces 2 | 3 | [%- FOREACH contract IN contracts -%] 4 | 5 | ## [% contract.name %] 6 | 7 | [% contract.natspec.notice -%] 8 | 9 | 10 | [% FOREACH item IN contract.contractItems -%] 11 | ### [% item.name %] 12 | 13 | [% item.natspec.notice %] 14 | 15 | [% indent(item.def) %] 16 | 17 | [% IF item.natspec.params -%] 18 | Parameters: 19 | 20 | [%- FOREACH param IN item.natspec.params %] 21 | * **[% param.name %]**: [% param.desc -%] 22 | [% END -%] 23 | 24 | [%- END -%] 25 | 26 | 27 | [% IF item.natspec.return -%] 28 | Returns: 29 | 30 | [%- FOREACH return IN item.natspec.return %] 31 | * [% markdownReturn(return) -%] 32 | [% END -%] 33 | 34 | [%- END -%] 35 | 36 | 37 | 38 | [%- END -%] 39 | 40 | [%- END -%] 41 | -------------------------------------------------------------------------------- /tasks/compileoverride.js: -------------------------------------------------------------------------------- 1 | // patch compilation ABIs; for functions with "staticDelegate" modifier, set state mutability to "view" 2 | // WARNING functions are matched by name only - all overloaded functions will also be patched 3 | 4 | // Also, verify that all module functions have nonReentrant or reentrantOK modifiers in place. 5 | 6 | subtask("compile:solidity:emit-artifacts").setAction(({ output }) => { 7 | const deepFindByProp = (o, key, val, path, cb) => { 8 | if (typeof o !== "object" || o === null) return; 9 | if (o[key] === val) return cb(o, path); 10 | Object.keys(o).forEach((k) => deepFindByProp(o[k], key, val, [...path, k], cb)); 11 | }; 12 | 13 | let numErrors = 0; 14 | 15 | deepFindByProp(output.sources, "kind", "function", [], (astFun, astPath) => { 16 | const contractFile = astPath[0]; 17 | 18 | if ( 19 | astFun.modifiers && 20 | astFun.modifiers.length > 0 && 21 | astFun.modifiers.find( 22 | (m) => m.modifierName && m.modifierName.name === "staticDelegate" 23 | ) 24 | ) { 25 | deepFindByProp( 26 | output.contracts[contractFile], 27 | "type", 28 | "function", 29 | [], 30 | (abiFun, abiPath) => { 31 | if (abiFun.name === astFun.name) { 32 | abiFun.stateMutability = "view"; 33 | 34 | const contractName = abiPath[0]; 35 | if (process.env.VERBOSE) { 36 | console.log( 37 | `${contractName}.${abiFun.name}: Patched ABI, state mutablity set to "view"` 38 | ); 39 | } 40 | } 41 | } 42 | ); 43 | } 44 | 45 | if ((contractFile.startsWith('contracts/modules/') || contractFile.match('^contracts/[^/]+[.]sol$')) && 46 | (astFun.visibility == 'external' || astFun.visibility == 'public') && 47 | (astFun.stateMutability !== 'view' && astFun.stateMutability !== 'pure') && 48 | astFun.implemented && // Ignore interface{} functions 49 | (contractFile !== 'contracts/modules/RiskManager.sol' && contractFile !== 'contracts/BaseIRM.sol') && // Internal modules 50 | (contractFile !== 'contracts/PToken.sol') // Not used in module system 51 | ) { 52 | 53 | const found = astFun.modifiers.find(m => m.modifierName && (m.modifierName.name === 'nonReentrant' || m.modifierName.name === 'reentrantOK' || m.modifierName.name === 'staticDelegate')); 54 | 55 | if (!found) { 56 | numErrors++; 57 | console.log(`ERROR: No reentrancy modifier found: ${contractFile}:${astFun.name}`); 58 | } 59 | } 60 | }); 61 | 62 | if (numErrors > 0) throw Error(`${numErrors} compilation errors`); 63 | 64 | return runSuper(); 65 | }); 66 | -------------------------------------------------------------------------------- /tasks/euler.js: -------------------------------------------------------------------------------- 1 | task("euler", "Interact with Euler contract") 2 | .addPositionalParam("designator", "contract.function") 3 | .addOptionalVariadicPositionalParam("args") 4 | .addFlag("callstatic") 5 | .addFlag("estimategas") 6 | .addFlag("isfork", "Run on localhost, which is already forked from mainnet") 7 | .addOptionalParam("forkat", "Fork mainnet at the given block. Only localhost network") 8 | .addOptionalParam("impersonate", "Impersonate account on mainnet fork") 9 | .setAction(async ({ designator, args, callstatic, estimategas, forkat, isfork, impersonate }) => { 10 | const et = require("../test/lib/eTestLib"); 11 | 12 | let ctx; 13 | if (forkat) await hre.run("debug:forkat", { forkat }); 14 | if (forkat || isfork || impersonate) ctx = await et.getTaskCtx('mainnet') 15 | else ctx = await et.getTaskCtx(); 16 | 17 | let components = designator.split('.'); 18 | let contract = ctx.contracts; 19 | while (components.length > 1) contract = contract[components.shift()]; 20 | let functionName = components[0]; 21 | 22 | let fragment = contract.interface.fragments.find(f => f.name === functionName); 23 | if (!fragment) throw(`no such function found: ${functionName}`); 24 | 25 | args = (args || []).map(a => { 26 | if (a === 'me') return ctx.wallet.address; 27 | if (a === 'euler') return ctx.contracts.euler.address; 28 | if (a === 'ref') return ctx.tokenSetup.riskManagerSettings.referenceAsset; 29 | if (a === 'max') return et.MaxUint256; 30 | if (a.startsWith('token:')) return ctx.contracts.tokens[a.split(':')[1]].address; 31 | if (a.startsWith('0x')) return a; 32 | if (!isNaN(parseFloat(a))) return ethers.BigNumber.from(parseFloat(a) + ''); 33 | return a; 34 | }); 35 | 36 | let res; 37 | 38 | try { 39 | if (fragment.constant) { 40 | res = await contract[functionName].apply(null, args); 41 | } else if (estimategas) { 42 | res = await contract.estimateGas[functionName].apply(null, args); 43 | } else if (callstatic) { 44 | res = await contract.callStatic[functionName].apply(null, args); 45 | } else { 46 | let signer = ctx.wallet; 47 | 48 | if (impersonate) { 49 | await network.provider.request({ 50 | method: "hardhat_impersonateAccount", 51 | params: [impersonate], 52 | }); 53 | signer = await ethers.getSigner(impersonate) 54 | } 55 | let estimateGasResult = await contract.connect(signer).estimateGas[functionName].apply(null, args); 56 | 57 | let opts = await ctx.txOpts(); 58 | args.push({ gasLimit: Math.floor(estimateGasResult * 1.01 + 1000), ...opts, }); 59 | 60 | let tx = await contract.connect(signer).functions[functionName].apply(null, args); 61 | console.log(`tx: ${tx.hash}`); 62 | res = await tx.wait(); 63 | 64 | if (impersonate) { 65 | await network.provider.request({ 66 | method: "hardhat_stopImpersonatingAccount", 67 | params: [impersonate], 68 | }); 69 | } 70 | } 71 | } catch (e) { 72 | console.error("ERROR"); 73 | console.error(e); 74 | process.exit(1); 75 | } 76 | 77 | console.log(et.dumpObj(res)); 78 | }); 79 | -------------------------------------------------------------------------------- /tasks/testoverride.js: -------------------------------------------------------------------------------- 1 | let doSkipFork; 2 | 3 | subtask("test:get-test-files") 4 | .setAction(async () => { 5 | let files = await runSuper(); 6 | 7 | if (doSkipFork || process.env.COVERAGE) { 8 | files = files.filter(f => !f.includes('-integration')); 9 | } 10 | return files; 11 | }); 12 | 13 | task("test") 14 | .addFlag("skipfork", "Skip tests on mainnet fork") 15 | .setAction(({ skipfork }) => { 16 | if (!process.env.ALCHEMY_API_KEY) { 17 | console.log('\nALCHEMY_API_KEY environment variable not found. Skipping integration tests on mainnet fork...\n'); 18 | doSkipFork = true; 19 | } else { 20 | doSkipFork = skipfork; 21 | } 22 | 23 | return runSuper(); 24 | }); 25 | 26 | task("coverage") 27 | .setAction(() => { 28 | console.log("\nMainnet fork tests currently not supported, skipping tests with `-integration` suffix...\n"); 29 | process.env.COVERAGE = true; 30 | return runSuper(); 31 | }); 32 | -------------------------------------------------------------------------------- /tasks/testtoken.js: -------------------------------------------------------------------------------- 1 | task("testtoken:deploy") 2 | .addPositionalParam("name") 3 | .addPositionalParam("symbol") 4 | .addOptionalParam("decimals", "decimals", "18") 5 | .setAction(async (args) => { 6 | const et = require("../test/lib/eTestLib"); 7 | const ctx = await et.getTaskCtx(); 8 | 9 | let tx = await ctx.factories.TestERC20.deploy(args.name, args.symbol, parseInt(args.decimals), true); 10 | console.log(`Transaction: ${tx.deployTransaction.hash}`); 11 | 12 | let result = await tx.deployed(); 13 | console.log(`Contract: ${result.address}`); 14 | }); 15 | 16 | task("testtoken:mint") 17 | .addPositionalParam("token") 18 | .addPositionalParam("who") 19 | .addPositionalParam("amount") 20 | .setAction(async (args) => { 21 | const et = require("../test/lib/eTestLib"); 22 | const ctx = await et.getTaskCtx(); 23 | 24 | let tok = await et.taskUtils.lookupToken(ctx, args.token); 25 | let who = await et.taskUtils.lookupAddress(ctx, args.who); 26 | 27 | let decimals = await tok.decimals(); 28 | 29 | await et.taskUtils.runTx(tok.mint(who, ethers.utils.parseUnits(args.amount, decimals))); 30 | }); 31 | 32 | task("testtoken:balanceOf") 33 | .addPositionalParam("token") 34 | .addPositionalParam("who") 35 | .setAction(async (args) => { 36 | const et = require("../test/lib/eTestLib"); 37 | const ctx = await et.getTaskCtx(); 38 | 39 | let tok = await et.taskUtils.lookupToken(ctx, args.token); 40 | let who = await et.taskUtils.lookupAddress(ctx, args.who); 41 | 42 | console.log(et.dumpObj(await tok.balanceOf(who))); 43 | }); 44 | 45 | task("testtoken:changeOwner") 46 | .addPositionalParam("token") 47 | .addPositionalParam("newOwner") 48 | .setAction(async (args) => { 49 | const et = require("../test/lib/eTestLib"); 50 | const ctx = await et.getTaskCtx(); 51 | 52 | let tok = await et.taskUtils.lookupToken(ctx, args.token); 53 | 54 | await et.taskUtils.runTx(tok.changeOwner(args.newOwner)); 55 | }); 56 | -------------------------------------------------------------------------------- /tasks/view.js: -------------------------------------------------------------------------------- 1 | task("view") 2 | .addPositionalParam("market") 3 | .setAction(async (args) => { 4 | const et = require("../test/lib/eTestLib"); 5 | const ctx = await et.getTaskCtx(); 6 | 7 | let market = await et.taskUtils.lookupToken(ctx, args.market); 8 | 9 | let res = await ctx.contracts.eulerGeneralView.callStatic.doQuery({ eulerContract: ctx.contracts.euler.address, account: et.AddressZero, markets: [market.address], }); 10 | 11 | console.log(et.dumpObj(res)); 12 | }); 13 | 14 | 15 | task("view:account") 16 | .addPositionalParam("addr") 17 | .setAction(async (args) => { 18 | const et = require("../test/lib/eTestLib"); 19 | const ctx = await et.getTaskCtx(); 20 | 21 | let res = await ctx.contracts.eulerGeneralView.callStatic.doQuery({ eulerContract: ctx.contracts.euler.address, account: args.addr, markets: [], }); 22 | 23 | console.log(et.dumpObj(res)); 24 | }); 25 | 26 | 27 | task("view:detailedLiquidity") 28 | .addPositionalParam("addr") 29 | .setAction(async (args) => { 30 | const et = require("../test/lib/eTestLib"); 31 | const ctx = await et.getTaskCtx(); 32 | 33 | let res = await ctx.contracts.exec.callStatic.detailedLiquidity(args.addr); 34 | 35 | console.log(et.dumpObj(res)); 36 | }); 37 | 38 | 39 | 40 | task("view:queryIRM") 41 | .addPositionalParam("market") 42 | .setAction(async (args) => { 43 | const et = require("../test/lib/eTestLib"); 44 | const ctx = await et.getTaskCtx(); 45 | 46 | let market = await et.taskUtils.lookupToken(ctx, args.market); 47 | 48 | let res = await ctx.contracts.eulerGeneralView.doQueryIRM({ eulerContract: ctx.contracts.euler.address, underlying: market.address, }); 49 | 50 | console.log(et.dumpObj(res)); 51 | }); 52 | -------------------------------------------------------------------------------- /test/decimalsAbove18.js: -------------------------------------------------------------------------------- 1 | const et = require('./lib/eTestLib'); 2 | 3 | et.testSet({ 4 | desc: "tokens with above 18 decimals", 5 | }) 6 | 7 | 8 | .test({ 9 | desc: "names, symbols and decimals", 10 | actions: ctx => [ 11 | { call: 'tokens.UTST.name', args: [], assertEql: 'Unactivated Test Token', }, 12 | { call: 'tokens.UTST.symbol', args: [], assertEql: 'UTST', }, 13 | { call: 'tokens.UTST.decimals', args: [], equals: [18], }, 14 | 15 | { from: ctx.wallet, send: 'tokens.UTST.changeDecimals', args: [19], }, 16 | { call: 'tokens.UTST.decimals', args: [], equals: [19], }, 17 | ], 18 | }) 19 | 20 | 21 | .test({ 22 | desc: "initial supplies and balances", 23 | actions: ctx => [ 24 | { from: ctx.wallet, send: 'tokens.UTST.changeDecimals', args: [19], }, 25 | 26 | { call: 'tokens.UTST.totalSupply', args: [], assertEql: et.units('0', 19), }, 27 | { call: 'tokens.UTST.balanceOf', args: [ctx.wallet.address], assertEql: et.units('0', 19) }, 28 | 29 | { from: ctx.wallet, send: 'tokens.UTST.mint', args: [ctx.wallet2.address, et.units('100', 19)] }, 30 | 31 | { call: 'tokens.UTST.balanceOf', args: [ctx.wallet2.address], assertEql: et.units('100', 19) }, 32 | ], 33 | }) 34 | 35 | 36 | .test({ 37 | desc: "activate market and setup default eToken", 38 | actions: ctx => [ 39 | { from: ctx.wallet, send: 'tokens.UTST.changeDecimals', args: [19], }, 40 | 41 | { from: ctx.wallet, send: 'markets.activateMarket', args: [ctx.contracts.tokens.UTST.address], expectError: 'e/too-many-decimals', }, 42 | 43 | {call: 'markets.underlyingToEToken', args: [ctx.contracts.tokens.UTST.address], assertEql: et.AddressZero }, 44 | ], 45 | }) 46 | 47 | 48 | .run(); -------------------------------------------------------------------------------- /test/flashLoanNative.js: -------------------------------------------------------------------------------- 1 | const et = require('./lib/eTestLib'); 2 | 3 | et.testSet({ 4 | desc: "flash loans native", 5 | 6 | preActions: ctx => { 7 | let actions = []; 8 | 9 | actions.push({ from: ctx.wallet, send: 'tokens.TST.approve', args: [ctx.contracts.euler.address, et.MaxUint256,], }); 10 | actions.push({ from: ctx.wallet, send: 'tokens.TST.mint', args: [ctx.wallet.address, et.eth(100)], }); 11 | actions.push({ from: ctx.wallet, send: 'eTokens.eTST.deposit', args: [0, et.eth(100)], }); 12 | 13 | actions.push({ action: 'updateUniswapPrice', pair: 'TST/WETH', price: '.05', }); 14 | 15 | return actions; 16 | }, 17 | }) 18 | 19 | 20 | .test({ 21 | desc: "did not pay back", 22 | actions: ctx => [ 23 | async () => { 24 | let errMsg; 25 | 26 | try { 27 | let tx = await ctx.contracts.flashLoanNativeTest.testFlashLoan({ 28 | eulerAddr: ctx.contracts.euler.address, 29 | marketsAddr: ctx.contracts.markets.address, 30 | execAddr: ctx.contracts.exec.address, 31 | underlying: ctx.contracts.tokens.TST.address, 32 | amount: et.eth(100), 33 | payItBack: false, 34 | }); 35 | 36 | await tx.wait(); 37 | } catch (e) { 38 | errMsg = e.message; 39 | } 40 | 41 | et.expect(errMsg).to.contain('e/collateral-violation'); 42 | }, 43 | 44 | { call: 'tokens.TST.balanceOf', args: [ctx.contracts.flashLoanNativeTest.address], assertEql: 0, }, 45 | { call: 'tokens.TST.balanceOf', args: [ctx.contracts.euler.address], assertEql: et.eth(100), }, 46 | ], 47 | }) 48 | 49 | 50 | .test({ 51 | desc: "does pay back", 52 | actions: ctx => [ 53 | async () => { 54 | let tx = await ctx.contracts.flashLoanNativeTest.testFlashLoan({ 55 | eulerAddr: ctx.contracts.euler.address, 56 | marketsAddr: ctx.contracts.markets.address, 57 | execAddr: ctx.contracts.exec.address, 58 | underlying: ctx.contracts.tokens.TST.address, 59 | amount: et.eth(100), 60 | payItBack: true, 61 | }); 62 | 63 | await tx.wait(); 64 | }, 65 | 66 | { call: 'tokens.TST.balanceOf', args: [ctx.contracts.flashLoanNativeTest.address], assertEql: 0, }, 67 | { call: 'tokens.TST.balanceOf', args: [ctx.contracts.euler.address], assertEql: et.eth(100), }, 68 | ], 69 | }) 70 | 71 | 72 | .test({ 73 | desc: "simplified, does not pay back", 74 | actions: ctx => [ 75 | { send: 'flashLoanNativeTest.testFlashLoan2', args: [ctx.contracts.tokens.TST.address, ctx.contracts.dTokens.dTST.address, ctx.contracts.euler.address, et.eth(50), et.eth(50).sub(1), ], expectError: 'e/flash-loan-not-repaid', }, 76 | 77 | { call: 'tokens.TST.balanceOf', args: [ctx.contracts.flashLoanNativeTest.address], assertEql: 0, }, 78 | { call: 'tokens.TST.balanceOf', args: [ctx.contracts.euler.address], assertEql: et.eth(100), }, 79 | ], 80 | }) 81 | 82 | .test({ 83 | desc: "simplified, more than avail", 84 | actions: ctx => [ 85 | { send: 'flashLoanNativeTest.testFlashLoan2', args: [ctx.contracts.tokens.TST.address, ctx.contracts.dTokens.dTST.address, ctx.contracts.euler.address, et.eth(500), et.eth(500).sub(1), ], expectError: 'ERC20: transfer amount exceeds balance', }, 86 | 87 | { call: 'tokens.TST.balanceOf', args: [ctx.contracts.flashLoanNativeTest.address], assertEql: 0, }, 88 | { call: 'tokens.TST.balanceOf', args: [ctx.contracts.euler.address], assertEql: et.eth(100), }, 89 | ], 90 | }) 91 | 92 | .test({ 93 | desc: "simplified, does pay back", 94 | actions: ctx => [ 95 | { send: 'flashLoanNativeTest.testFlashLoan2', args: [ctx.contracts.tokens.TST.address, ctx.contracts.dTokens.dTST.address, ctx.contracts.euler.address, et.eth(50), et.eth(50), ], }, 96 | 97 | { call: 'tokens.TST.balanceOf', args: [ctx.contracts.flashLoanNativeTest.address], assertEql: 0, }, 98 | { call: 'tokens.TST.balanceOf', args: [ctx.contracts.euler.address], assertEql: et.eth(100), }, 99 | ], 100 | }) 101 | 102 | 103 | .run(); 104 | -------------------------------------------------------------------------------- /test/gas.js: -------------------------------------------------------------------------------- 1 | const et = require('./lib/eTestLib'); 2 | 3 | et.testSet({ 4 | desc: "gas tests", 5 | 6 | preActions: ctx => { 7 | let actions = []; 8 | 9 | for (let from of [ctx.wallet, ctx.wallet2, ctx.wallet3]) { 10 | actions.push({ from, send: 'tokens.TST.approve', args: [ctx.contracts.euler.address, et.MaxUint256,], }); 11 | actions.push({ from, send: 'tokens.TST2.approve', args: [ctx.contracts.euler.address, et.MaxUint256,], }); 12 | } 13 | 14 | for (let from of [ctx.wallet, ctx.wallet2, ctx.wallet3]) { 15 | actions.push({ from, send: 'tokens.TST.mint', args: [from.address, et.eth(100)], }); 16 | } 17 | 18 | for (let from of [ctx.wallet, ctx.wallet2, ctx.wallet3]) { 19 | actions.push({ from, send: 'tokens.TST2.mint', args: [from.address, et.eth(100)], }); 20 | } 21 | 22 | actions.push({ from: ctx.wallet, send: 'eTokens.eTST.deposit', args: [0, et.eth(1)], }); 23 | 24 | actions.push({ from: ctx.wallet2, send: 'eTokens.eTST2.deposit', args: [0, et.eth(50)], }); 25 | actions.push({ from: ctx.wallet2, send: 'markets.enterMarket', args: [0, ctx.contracts.tokens.TST2.address], },); 26 | 27 | actions.push({ action: 'updateUniswapPrice', pair: 'TST/WETH', price: '.01', }); 28 | actions.push({ action: 'updateUniswapPrice', pair: 'TST2/WETH', price: '.05', }); 29 | 30 | actions.push({ action: 'jumpTime', time: 31*60, }); 31 | 32 | return actions; 33 | }, 34 | }) 35 | 36 | 37 | .test({ 38 | desc: "simple gas", 39 | actions: ctx => [ 40 | { from: ctx.wallet2, send: 'dTokens.dTST.borrow', args: [0, et.eth(.4)], }, 41 | 42 | 43 | { from: ctx.wallet3, send: 'eTokens.eTST2.deposit', args: [0, et.eth(50)], }, 44 | { from: ctx.wallet3, send: 'markets.enterMarket', args: [0, ctx.contracts.tokens.TST2.address], }, 45 | { from: ctx.wallet3, send: 'dTokens.dTST.borrow', args: [0, et.eth(.1)], }, 46 | { from: ctx.wallet3, send: 'dTokens.dTST.repay', args: [0, et.eth(.1)], }, 47 | ], 48 | }) 49 | 50 | 51 | 52 | 53 | .run(); 54 | -------------------------------------------------------------------------------- /test/irmLinearKink.js: -------------------------------------------------------------------------------- 1 | const et = require('./lib/eTestLib'); 2 | const scenarios = require('./lib/scenarios'); 3 | 4 | 5 | function apy(v, tolerance) { 6 | let apr = Math.log(v + 1); 7 | 8 | let spy = ethers.BigNumber.from(Math.floor(apr * 1e6)) 9 | .mul(ethers.BigNumber.from(10).pow(27 - 6)) 10 | .div(et.SecondsPerYear); 11 | 12 | return spy; 13 | } 14 | 15 | function apyInterpolate(apy, frac) { 16 | return Math.exp(Math.log(1 + apy) * frac) - 1; 17 | } 18 | 19 | 20 | 21 | et.testSet({ 22 | desc: "irm linear kink", 23 | 24 | preActions: scenarios.basicLiquidity(), 25 | }) 26 | 27 | 28 | 29 | .test({ 30 | desc: "APRs", 31 | actions: ctx => [ 32 | { action: 'setIRM', underlying: 'TST2', irm: 'IRM_DEFAULT', }, 33 | { action: 'setReserveFee', underlying: 'TST2', fee: 0, }, 34 | 35 | // 0% utilisation 36 | { call: 'markets.interestRate', args: [ctx.contracts.tokens.TST2.address], equals: [apy(0), 1e-5], }, 37 | 38 | // 25% utilisation 39 | { send: 'dTokens.dTST2.borrow', args: [0, et.eth(2.5)], }, 40 | { call: 'markets.interestRate', args: [ctx.contracts.tokens.TST2.address], equals: [apy(apyInterpolate(.1, .5)), 1e-5], }, 41 | 42 | // 50% utilisation 43 | { send: 'dTokens.dTST2.borrow', args: [0, et.eth(2.5)], }, 44 | { call: 'markets.interestRate', args: [ctx.contracts.tokens.TST2.address], equals: [apy(.1), 1e-5], }, 45 | 46 | // 75% utilisation 47 | { send: 'dTokens.dTST2.borrow', args: [0, et.eth(2.5)], }, 48 | { call: 'markets.interestRate', args: [ctx.contracts.tokens.TST2.address], equals: [apy(3).sub(apy(.1)).div(2).add(apy(.1)), 1e-5], }, 49 | 50 | // 100% utilisation 51 | { send: 'dTokens.dTST2.borrow', args: [0, et.eth(2.5)], }, 52 | { call: 'markets.interestRate', args: [ctx.contracts.tokens.TST2.address], equals: [apy(3), 1e-4], }, 53 | ], 54 | }) 55 | 56 | 57 | 58 | .run(); 59 | -------------------------------------------------------------------------------- /test/lib/deployLib.js: -------------------------------------------------------------------------------- 1 | async function verifyBatch(verification) { 2 | 3 | if (Object.keys(verification.contracts.tokens).length > 0) { 4 | console.log("Verifying test tokens"); 5 | for (let token of Object.keys(verification.contracts.tokens)) { 6 | console.log(token, verification.contracts.tokens[token].address, verification.contracts.tokens[token].args, verification.contracts.tokens[token].contractPath); 7 | await verifyContract(verification.contracts.tokens[token].address, verification.contracts.tokens[token].args, verification.contracts.tokens[token].contractPath); 8 | } 9 | } 10 | 11 | if (Object.keys(verification.contracts.modules).length > 0) { 12 | console.log("Verifying modules"); 13 | for (let module of Object.keys(verification.contracts.modules)) { 14 | console.log(module, verification.contracts.modules[module].address, verification.contracts.modules[module].args, verification.contracts.modules[module].contractPath); 15 | await verifyContract(verification.contracts.modules[module].address, verification.contracts.modules[module].args, verification.contracts.modules[module].contractPath); 16 | } 17 | } 18 | 19 | if (Object.keys(verification.contracts.swapHandlers).length > 0) { 20 | console.log("Verifying swap handlers"); 21 | for (let handler of Object.keys(verification.contracts.swapHandlers)) { 22 | console.log(handler, verification.contracts.swapHandlers[handler].address, verification.contracts.swapHandlers[handler].args, verification.contracts.swapHandlers[handler].contractPath); 23 | await verifyContract(verification.contracts.swapHandlers[handler].address, verification.contracts.swapHandlers[handler].args, verification.contracts.swapHandlers[handler].contractPath); 24 | } 25 | } 26 | 27 | if (Object.keys(verification.contracts).length > 0) { 28 | console.log("Verifying euler contracts"); 29 | for (let contract of Object.keys(verification.contracts)) { 30 | if (verification.contracts[contract].address && verification.contracts[contract].args) { 31 | console.log(contract, verification.contracts[contract].address, verification.contracts[contract].args, verification.contracts[contract].contractPath); 32 | await verifyContract(verification.contracts[contract].address, verification.contracts[contract].args, verification.contracts[contract].contractPath); 33 | } 34 | } 35 | } 36 | 37 | } 38 | 39 | async function verifyContract(contractAddress, contractArgs, contractPath = null) { 40 | try { 41 | if (contractPath) { 42 | await run("verify:verify", { 43 | address: contractAddress, 44 | constructorArguments: [...contractArgs], 45 | contract: contractPath 46 | }); 47 | } else { 48 | await run("verify:verify", { 49 | address: contractAddress, 50 | constructorArguments: [...contractArgs], 51 | }); 52 | } 53 | 54 | } catch (error) { 55 | console.log(`Smart contract verification for contract at ${contractAddress}, was not successful\n ${error.message}`); 56 | } 57 | } 58 | 59 | module.exports = { 60 | verifyBatch, 61 | verifyContract 62 | } 63 | -------------------------------------------------------------------------------- /test/lib/merkle-tree.js: -------------------------------------------------------------------------------- 1 | const ethers = require('ethers'); 2 | 3 | 4 | function processDistribution(dist) { 5 | dist = dist.map(d => { 6 | return { 7 | account: d.account.toLowerCase(), 8 | token: d.token.toLowerCase(), 9 | claimable: d.claimable, 10 | leaf: ethers.utils.concat([ d.account, d.token, ethers.utils.hexZeroPad(d.claimable, 32) ]), 11 | }; 12 | }); 13 | 14 | return dist.sort((a,b) => Buffer.compare(a.leaf, b.leaf)); 15 | } 16 | 17 | function hashLevel(level) { 18 | let nextLevel = []; 19 | 20 | for (let i = 0; i < level.length; i += 2) { 21 | if (i === level.length - 1) nextLevel.push(level[i]); // odd number of nodes at this level 22 | else nextLevel.push(ethers.utils.keccak256(ethers.utils.concat([level[i], level[i+1]].sort()))); 23 | } 24 | 25 | return nextLevel; 26 | } 27 | 28 | function root(items) { 29 | if (items.length === 0) throw("can't build merkle tree with empty items"); 30 | 31 | items = processDistribution(items); 32 | let level = items.map(d => ethers.utils.keccak256(d.leaf)); 33 | 34 | while (level.length > 1) { 35 | level = hashLevel(level); 36 | } 37 | 38 | return level[0]; 39 | } 40 | 41 | function proof(items, account, token) { 42 | account = account.toLowerCase(); 43 | token = token.toLowerCase(); 44 | 45 | items = processDistribution(items); 46 | let level = items.map(d => ethers.utils.keccak256(d.leaf)); 47 | 48 | let origIndex = items.findIndex((i) => i.account === account && i.token == token); 49 | if (origIndex === -1) throw("item not found in items: " + item); 50 | 51 | let witnesses = []; 52 | let index = origIndex; 53 | 54 | while (level.length > 1) { 55 | let nextIndex = Math.floor(index / 2); 56 | 57 | if (nextIndex * 2 === index) { // left side 58 | if (index < level.length - 1) { // only if we're not the last in a level with odd number of nodes 59 | witnesses.push(level[index + 1]); 60 | } 61 | } else { // right side 62 | witnesses.push(level[index - 1]); 63 | } 64 | 65 | index = nextIndex; 66 | level = hashLevel(level); 67 | } 68 | 69 | return { 70 | item: items[origIndex], 71 | witnesses, 72 | }; 73 | } 74 | 75 | 76 | module.exports = { 77 | root, 78 | proof, 79 | }; 80 | -------------------------------------------------------------------------------- /test/lib/sqrtPriceUtils.js: -------------------------------------------------------------------------------- 1 | const ethers = require('ethers'); 2 | const bn = require('bignumber.js'); 3 | 4 | bn.config({ EXPONENTIAL_AT: 999999, DECIMAL_PLACES: 40 }) 5 | 6 | 7 | 8 | function ratioToSqrtPriceX96(a, b) { 9 | return ethers.BigNumber.from( 10 | new bn(a.toString()) 11 | .div(b.toString()) 12 | .sqrt() 13 | .multipliedBy(new bn(2).pow(96)) 14 | .integerValue(3) 15 | .toString() 16 | ); 17 | } 18 | 19 | function sqrtPriceX96ToPrice(a, invert) { 20 | let c1e18 = ethers.BigNumber.from('10').pow(18); 21 | let scale = ethers.BigNumber.from(2).pow(96*2).div(c1e18); 22 | a = ethers.BigNumber.from(a); 23 | a = a.mul(a).div(scale); 24 | if (invert) a = c1e18.mul(c1e18).div(a); 25 | return new bn(a.toString()).div('1e18').toString(); 26 | } 27 | 28 | 29 | module.exports = { 30 | ratioToSqrtPriceX96, 31 | sqrtPriceX96ToPrice, 32 | }; 33 | -------------------------------------------------------------------------------- /test/lib/token-setups/goerli.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | riskManagerSettings: { 3 | referenceAsset: '0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6', 4 | uniswapFactory: '0x288be1A33bcdfA9A09cCa95CA1eD628A5294e82c', 5 | uniswapPoolInitCodeHash: '0xc02f72e8ae5e68802e6d893d58ddfb0df89a2f4c9c2f04927db1186a29373660', 6 | }, 7 | 8 | existingTokens: { 9 | DAI: { 10 | address: '0x848840e2d0bcb5c6ce530de671ef97fd64bea6db', 11 | }, 12 | USDC: { 13 | address: '0x94b348EdFE1f989Fc7a49CF058DE47A60746Ae43', 14 | }, 15 | USDT: { 16 | address: '0x86358D04992019356e893DCe2Ab31DFDc61c83A4', 17 | }, 18 | WBTC: { 19 | address: '0x25c2ad80e2213434d161B4e1648dF9A6D356157A', 20 | }, 21 | UNI: { 22 | address: '0x1675E1Da3c621AF02102cdE23d70ba7D49Df94d2', 23 | }, 24 | COMP: { 25 | address: '0xf806E9732D2ab949B493E47Df4b8180A47fa13eb', 26 | }, 27 | REP: { 28 | address: '0x104114b31a9e93C55645F1E5D54FD370b388fB66', 29 | }, 30 | BZRX: { 31 | address: '0x4Ab905A0E1AdC9D9Dba66668b812D749DEA3620d', 32 | }, 33 | DOUGH: { 34 | address: '0xb1222EFBA63F8C64cff04970749c266b5c6646D4', 35 | }, 36 | CRV: { 37 | address: '0x83700f43C9Cf7cf6A1714641e6EB02c848BaaD77', 38 | }, 39 | }, 40 | }; 41 | -------------------------------------------------------------------------------- /test/lib/token-setups/kovan.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | riskManagerSettings: { 3 | referenceAsset: '0xd0A1E359811322d97991E03f863a0C30C2cF029C', 4 | uniswapFactory: '0x58f6b77148BE49BF7898472268ae8f26377d0AA6', 5 | uniswapPoolInitCodeHash: '0xc02f72e8ae5e68802e6d893d58ddfb0df89a2f4c9c2f04927db1186a29373660', 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /test/lib/token-setups/mainnet.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | riskManagerSettings: { 3 | referenceAsset: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', 4 | uniswapFactory: '0x1F98431c8aD98523631AE4a59f267346ea31F984', 5 | uniswapPoolInitCodeHash: '0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54', 6 | }, 7 | 8 | existingContracts: { 9 | swapRouterV2: '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D', 10 | swapRouterV3: '0xE592427A0AEce92De3Edee1F18E0157C05861564', 11 | swapRouter02: '0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45', 12 | oneInch: '0x1111111254fb6c44bAC0beD2854e76F90643097d', 13 | eulToken: '0xd9fcd98c322942075a5c3860693e9f4f03aae07b', 14 | chainlinkAggregator_STETH_ETH: '0x86392dc19c0b719886221c78ab11eb8cf5c52812', 15 | chainlinkAggregator_ETH_USD: '0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419', 16 | chainlinkAggregator_MATIC_USD: '0x7bAC85A8a13A4BcD8abb3eB7d6b4d632c5a57676', 17 | chainlinkAggregator_ENS_USD: '0x5C00128d4d1c2F4f652C267d7bcdD7aC99C16E16', 18 | chainlinkAggregator_MIM_USD: '0x7A364e8770418566e3eb2001A96116E6138Eb32F', 19 | chainlinkAggregator_IMX_USD: '0xBAEbEFc1D023c0feCcc047Bff42E75F15Ff213E6', 20 | chainlinkAggregator_LUSD_USD: '0x3D7aE7E594f2f2091Ad8798313450130d0Aba3a0', 21 | chainlinkAggregator_REQ_USD: '0x2F05888D185970f178f40610306a0Cc305e52bBF', 22 | chainlinkAggregator_FXS_USD: '0x6Ebc52C8C1089be9eB3945C4350B68B8E4C2233f', 23 | chainlinkAggregator_WBTC_BTC: '0xfdFD9C85aD200c506Cf9e21F1FD8dd01932FBB23', 24 | chainlinkAggregator_BTC_ETH: '0xdeb288F737066589598e9214E782fa5A8eD689e8', 25 | chainlinkAggregator_XCN_USD: '0xeb988B77b94C186053282BfcD8B7ED55142D3cAB', 26 | }, 27 | 28 | existingTokens: { 29 | WETH: { 30 | address: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', 31 | }, 32 | USDC: { 33 | address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', 34 | }, 35 | DAI: { 36 | address: '0x6b175474e89094c44da98b954eedeac495271d0f', 37 | }, 38 | STETH: { 39 | address: '0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84', 40 | }, 41 | WBTC: { 42 | address: "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599" 43 | }, 44 | }, 45 | }; 46 | -------------------------------------------------------------------------------- /test/lib/token-setups/ropsten.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | riskManagerSettings: { 3 | referenceAsset: '0xc778417E063141139Fce010982780140Aa0cD5Ab', 4 | uniswapFactory: '0x1F98431c8aD98523631AE4a59f267346ea31F984', 5 | uniswapPoolInitCodeHash: '0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54', 6 | }, 7 | 8 | existingContracts: { 9 | swapRouterV2: '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D', 10 | swapRouterV3: '0xE592427A0AEce92De3Edee1F18E0157C05861564', 11 | swapRouter02: '0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45', 12 | oneInch: '0x0000000000000000000000000000000000000000', 13 | eulToken: '0x2fee9f774f8d963bf253d41111d03ae990b0834d', 14 | }, 15 | 16 | existingTokens: { 17 | DAI: { 18 | address: '0xB7fe2334CD47383C17bfb97B09823F11cc1A91B8', 19 | }, 20 | USDC: { 21 | address: '0x95689Faeed6691757Df1AD48B7beA1B8Acf2dABe' 22 | }, 23 | USDT: { 24 | address: '0xCAfC3274Ba43825fCDCcE3D3263132A399658C7D' 25 | }, 26 | WBTC: { 27 | address: '0x318010fe8ee7c627e60dcfBF52A16fA79c22ad5F' 28 | }, 29 | UNI: { 30 | address: '0x5D4553bc5dE02216322306A8f5ed8398eCB6d411' 31 | }, 32 | COMP: { 33 | address: '0x604a8d0AAdD03AF55Fbf4445468d6bC90A94aF8B' 34 | }, 35 | REP: { 36 | address: '0x19bBa4A58dD289635c48a407542D5B815e4f7094' 37 | }, 38 | BZRX: { 39 | address: '0x92470540255656BA36EF50fC9dba77011922284d' 40 | }, 41 | DOUGH: { 42 | address: '0x6F37e457464B7D605F468FAB68F4f64ac48C2dEa' 43 | }, 44 | }, 45 | }; 46 | -------------------------------------------------------------------------------- /test/lib/token-setups/staging.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testing: { 3 | tokens: [ 4 | { 5 | name: "Wrapped ETH", 6 | symbol: "WETH", 7 | decimals: 18, 8 | config: { 9 | collateralFactor: 0.9, 10 | borrowIsolated: false, 11 | }, 12 | }, 13 | { 14 | name: "DAI", 15 | symbol: "DAI", 16 | decimals: 18, 17 | config: { 18 | collateralFactor: 0.75, 19 | borrowIsolated: false, 20 | }, 21 | }, 22 | { 23 | name: "USD Coin", 24 | symbol: "USDC", 25 | decimals: 6, 26 | config: { 27 | collateralFactor: 0.85, 28 | borrowIsolated: false, 29 | }, 30 | }, 31 | { 32 | name: "Basic Attention Token", 33 | symbol: "BAT", 34 | decimals: 18, 35 | }, 36 | { 37 | name: "Chainlink", 38 | symbol: "LINK", 39 | decimals: 18, 40 | }, 41 | { 42 | name: "Uniswap Token", 43 | symbol: "UNI", 44 | decimals: 18, 45 | config: { 46 | borrowIsolated: false, 47 | }, 48 | }, 49 | { 50 | name: "yearn.finance", 51 | symbol: "YFI", 52 | decimals: 18, 53 | }, 54 | { 55 | name: "Compound", 56 | symbol: "COMP", 57 | decimals: 18, 58 | config: { 59 | collateralFactor: 0.5, 60 | borrowIsolated: false, 61 | }, 62 | }, 63 | { 64 | name: "Euler Token", 65 | symbol: "EUL", 66 | decimals: 18, 67 | }, 68 | ], 69 | 70 | useRealUniswap: true, 71 | 72 | uniswapPools: [ 73 | ["DAI", "WETH"], 74 | ["USDC", "WETH"], 75 | ["BAT", "WETH"], 76 | ["LINK", "WETH"], 77 | ["UNI", "WETH"], 78 | ["YFI", "WETH"], 79 | ["COMP", "WETH"], 80 | ], 81 | 82 | activated: [ 83 | "WETH", 84 | "DAI", 85 | "USDC", 86 | "BAT", 87 | "LINK", 88 | "UNI", 89 | "YFI", 90 | "COMP", 91 | ], 92 | }, 93 | }; 94 | -------------------------------------------------------------------------------- /test/lib/token-setups/testing-real-uniswap-activated.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testing: { 3 | tokens: [ 4 | { 5 | name: "Wrapped ETH", 6 | symbol: "WETH", 7 | decimals: 18, 8 | config: { 9 | collateralFactor: 0.75, 10 | borrowIsolated: false, 11 | }, 12 | }, 13 | { 14 | name: "Test Token", 15 | symbol: "TST", 16 | decimals: 18, 17 | config: { 18 | collateralFactor: 0.75, 19 | borrowIsolated: false, 20 | }, 21 | }, 22 | { 23 | name: "Test Token 2", 24 | symbol: "TST2", 25 | decimals: 18, 26 | config: { 27 | collateralFactor: 0.75, 28 | borrowIsolated: false, 29 | }, 30 | }, 31 | { 32 | name: "Test Token 3", 33 | symbol: "TST3", 34 | decimals: 18, 35 | config: { 36 | collateralFactor: 0.75, 37 | borrowIsolated: false, 38 | }, 39 | }, 40 | { 41 | name: "Test Token 4", 42 | symbol: "TST4", 43 | decimals: 6, 44 | }, 45 | { 46 | name: "Uninited Test Token 3", 47 | symbol: "UTST", 48 | decimals: 18, 49 | } 50 | ], 51 | 52 | useRealUniswap: true, 53 | 54 | uniswapPools: [ 55 | ["TST", "WETH"], 56 | ["TST2", "WETH"], 57 | ["TST3", "WETH"], 58 | ["TST4", "WETH"], 59 | ["TST2", "TST3"], 60 | ["TST4", "TST"], 61 | ], 62 | 63 | activated: [ 64 | "WETH", 65 | "TST", 66 | "TST2", 67 | "TST3", 68 | "TST4", 69 | ], 70 | }, 71 | }; 72 | -------------------------------------------------------------------------------- /test/lib/token-setups/testing-real-uniswap.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testing: { 3 | tokens: [ 4 | { 5 | name: "Wrapped ETH", 6 | symbol: "WETH", 7 | decimals: 18, 8 | }, 9 | { 10 | name: "Test Token", 11 | symbol: "TST", 12 | decimals: 18, 13 | }, 14 | { 15 | name: "Test Token 2", 16 | symbol: "TST2", 17 | decimals: 6, 18 | }, 19 | { 20 | name: "Test Token 3", 21 | symbol: "TST3", 22 | decimals: 0, 23 | }, 24 | { 25 | name: "Test Token 4", 26 | symbol: "TST4", 27 | decimals: 18, 28 | }, 29 | { 30 | name: "Test Token 5", 31 | symbol: "TST5", 32 | decimals: 18, 33 | }, 34 | { 35 | name: "Test Token 6", 36 | symbol: "TST6", 37 | decimals: 6, 38 | }, 39 | ], 40 | 41 | useRealUniswap: true, 42 | 43 | uniswapPools: [ 44 | ["TST", "WETH"], 45 | ["TST2", "WETH"], 46 | ["TST3", "WETH"], 47 | ["TST6", "WETH"], 48 | ], 49 | 50 | activated: [ 51 | ], 52 | }, 53 | }; 54 | -------------------------------------------------------------------------------- /test/lib/token-setups/testing.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testing: { 3 | tokens: [ 4 | { 5 | name: "Wrapped ETH", 6 | symbol: "WETH", 7 | decimals: 18, 8 | config: { 9 | collateralFactor: 0.75, 10 | borrowIsolated: false, 11 | }, 12 | }, 13 | { 14 | name: "Test Token", 15 | symbol: "TST", 16 | decimals: 18, 17 | config: { 18 | collateralFactor: 0.75, 19 | borrowIsolated: false, 20 | }, 21 | }, 22 | { 23 | name: "Test Token 2", 24 | symbol: "TST2", 25 | decimals: 18, 26 | config: { 27 | collateralFactor: 0.75, 28 | }, 29 | }, 30 | { 31 | name: "Test Token 3", 32 | symbol: "TST3", 33 | decimals: 18, 34 | }, 35 | { 36 | name: "Test Token 4", 37 | symbol: "TST4", 38 | decimals: 18, 39 | }, 40 | { 41 | name: "Test Token 5", 42 | symbol: "TST5", 43 | decimals: 18, 44 | }, 45 | { 46 | name: "Test Token 6", 47 | symbol: "TST6", 48 | decimals: 18, 49 | }, 50 | { 51 | name: "Test Token 7", 52 | symbol: "TST7", 53 | decimals: 18, 54 | }, 55 | { 56 | name: "Test Token 8", 57 | symbol: "TST8", 58 | decimals: 18, 59 | }, 60 | { 61 | name: "Test Token 9", 62 | symbol: "TST9", 63 | decimals: 6, 64 | }, 65 | { 66 | name: "Test Token 10", 67 | symbol: "TST10", 68 | decimals: 0, 69 | }, 70 | { 71 | name: "Unactivated Test Token", 72 | symbol: "UTST", 73 | decimals: 18, 74 | }, 75 | { 76 | name: "Test Token 11", 77 | symbol: "TST11", 78 | decimals: 18, 79 | }, 80 | { 81 | name: "Test Token 12", 82 | symbol: "TST12", 83 | decimals: 8, 84 | }, 85 | { 86 | name: "Test Token 13", 87 | symbol: "TST13", 88 | decimals: 18, 89 | }, 90 | { 91 | name: "Test Token 14", 92 | symbol: "TST14", 93 | decimals: 18, 94 | }, 95 | { 96 | name: "Euler Token", 97 | symbol: "EUL", 98 | decimals: 18, 99 | }, 100 | ], 101 | 102 | uniswapPools: [ 103 | ["TST", "WETH"], 104 | ["TST2", "WETH"], 105 | ["TST3", "WETH"], 106 | ["TST6", "WETH"], 107 | ["TST9", "WETH"], 108 | ["TST10", "WETH"], 109 | ["TST11", "WETH"], 110 | ["TST12", "WETH"], 111 | ["TST13", "WETH"], 112 | ["TST14", "WETH"], 113 | ["UTST", "WETH"], 114 | ], 115 | 116 | activated: [ 117 | "WETH", 118 | "TST", 119 | "TST2", 120 | "TST3", 121 | "TST6", // TST6 address is the first one < the WETH address which exercises uniswap's address sorting 122 | "TST9", // Has 6 decimals 123 | "TST10", // Has 0 decimals 124 | "TST11", // Has 18 decimals 125 | "TST12", // Has 8 decimals 126 | "TST13", // Has 18 decimals 127 | "TST14", // Has 18 decimals 128 | ], 129 | }, 130 | }; 131 | -------------------------------------------------------------------------------- /test/marketViews.js: -------------------------------------------------------------------------------- 1 | const et = require('./lib/eTestLib'); 2 | 3 | const badAddr = '0x1111111111111111111111111111111111111111'; 4 | 5 | et.testSet({ 6 | desc: "market view functions", 7 | }) 8 | 9 | 10 | .test({ 11 | desc: "test market views with invalid tokens", 12 | actions: ctx => [ 13 | // Non-failing cases 14 | 15 | { call: 'markets.underlyingToEToken', args: [badAddr], assertEql: et.AddressZero, }, 16 | { call: 'markets.underlyingToDToken', args: [badAddr], assertEql: et.AddressZero, }, 17 | { call: 'markets.underlyingToPToken', args: [badAddr], assertEql: et.AddressZero, }, 18 | 19 | // Failing cases 20 | 21 | { call: 'markets.underlyingToAssetConfig', args: [badAddr], expectError: 'e/market-not-activated', }, 22 | { call: 'markets.underlyingToAssetConfigUnresolved', args: [badAddr], expectError: 'e/market-not-activated', }, 23 | { call: 'markets.eTokenToUnderlying', args: [badAddr], expectError: 'e/invalid-etoken', }, 24 | { call: 'markets.eTokenToDToken', args: [badAddr], expectError: 'e/invalid-etoken', }, 25 | { call: 'markets.dTokenToUnderlying', args: [badAddr], expectError: 'e/invalid-dtoken', }, 26 | { call: 'markets.interestRateModel', args: [badAddr], expectError: 'e/market-not-activated', }, 27 | { call: 'markets.interestRate', args: [badAddr], expectError: 'e/market-not-activated', }, 28 | { call: 'markets.interestAccumulator', args: [badAddr], expectError: 'e/market-not-activated', }, 29 | { call: 'markets.reserveFee', args: [badAddr], expectError: 'e/market-not-activated', }, 30 | { call: 'markets.getPricingConfig', args: [badAddr], expectError: 'e/market-not-activated', }, 31 | ], 32 | }) 33 | 34 | 35 | 36 | .run(); 37 | -------------------------------------------------------------------------------- /test/mintBurn.js: -------------------------------------------------------------------------------- 1 | const et = require('./lib/eTestLib'); 2 | const scenarios = require('./lib/scenarios'); 3 | 4 | 5 | et.testSet({ 6 | desc: "minting and burning", 7 | 8 | preActions: scenarios.basicLiquidity(), 9 | }) 10 | 11 | 12 | 13 | .test({ 14 | desc: "no liquidity", 15 | actions: ctx => [ 16 | { from: ctx.wallet4, send: 'eTokens.eTST.mint', args: [0, et.eth(1)], expectError: 'e/collateral-violation', }, 17 | ], 18 | }) 19 | 20 | 21 | .test({ 22 | desc: "borrow on empty pool, and repay", 23 | actions: ctx => [ 24 | { action: 'setIRM', underlying: 'TST3', irm: 'IRM_ZERO', }, 25 | 26 | { call: 'eTokens.eTST3.totalSupply', equal: et.formatUnits(et.DefaultReserve), }, 27 | { call: 'dTokens.dTST3.totalSupply', assertEql: 0, }, 28 | 29 | { from: ctx.wallet, send: 'eTokens.eTST3.mint', args: [0, et.eth(1)], }, 30 | 31 | { call: 'eTokens.eTST3.balanceOfUnderlying', args: [ctx.wallet.address], assertEql: et.eth('0.999999999999'), }, 32 | { call: 'dTokens.dTST3.balanceOf', args: [ctx.wallet.address], assertEql: et.eth(1), }, 33 | 34 | { from: ctx.wallet, send: 'eTokens.eTST3.burn', args: [0, et.eth(1)], expectError: 'e/insufficient-balance'}, 35 | { from: ctx.wallet, send: 'eTokens.eTST3.burn', args: [0, et.eth('0.999999999999')], }, 36 | 37 | { call: 'dTokens.dTST3.balanceOf', args: [ctx.wallet.address], equals: et.BN(et.DefaultReserve), }, 38 | 39 | { send: 'tokens.TST3.approve', args: [ctx.contracts.euler.address, et.MaxUint256,], }, 40 | { send: 'tokens.TST3.mint', args: [ctx.wallet.address, et.eth(1)], }, 41 | { send: 'eTokens.eTST3.deposit', args: [0, et.eth(1)], }, 42 | 43 | { from: ctx.wallet, send: 'eTokens.eTST3.burn', args: [0, et.BN(et.DefaultReserve)], }, 44 | 45 | { call: 'eTokens.eTST3.balanceOfUnderlying', args: [ctx.wallet.address], assertEql: et.eth('0.999999999999'), }, 46 | { call: 'dTokens.dTST3.balanceOf', args: [ctx.wallet.address], assertEql: 0, }, 47 | { call: 'eTokens.eTST3.totalSupply', assertEql: et.eth(1), }, 48 | { call: 'dTokens.dTST3.totalSupply', assertEql: 0, }, 49 | ], 50 | }) 51 | 52 | 53 | 54 | .run(); 55 | -------------------------------------------------------------------------------- /test/rebasing.js: -------------------------------------------------------------------------------- 1 | const et = require('./lib/eTestLib'); 2 | 3 | et.testSet({ 4 | desc: "rebasing tokens", 5 | 6 | preActions: ctx => { 7 | let actions = []; 8 | 9 | for (let from of [ctx.wallet, ctx.wallet2, ctx.wallet3]) { 10 | actions.push({ from, send: 'tokens.TST.mint', args: [from.address, et.eth(10)], }); 11 | actions.push({ from, send: 'tokens.TST.approve', args: [ctx.contracts.euler.address, et.MaxUint256,], }); 12 | } 13 | 14 | return actions; 15 | }, 16 | }) 17 | 18 | 19 | .test({ 20 | desc: "balances scale proportionally", 21 | actions: ctx => [ 22 | { from: ctx.wallet, send: 'eTokens.eTST.deposit', args: [0, et.eth(10)], }, 23 | { from: ctx.wallet2, send: 'eTokens.eTST.deposit', args: [0, et.eth(10)], }, 24 | 25 | { call: 'eTokens.eTST.balanceOfUnderlying', args: [ctx.wallet.address], equals: [et.eth(10), et.formatUnits(et.DefaultReserve)], }, 26 | // Second user just loses 1 wei due to rounding 27 | { call: 'eTokens.eTST.balanceOfUnderlying', args: [ctx.wallet2.address], equals: [et.eth(10), et.formatUnits(et.DefaultReserve)], }, 28 | { call: 'tokens.TST.balanceOf', args: [ctx.wallet3.address], equals: et.eth(10), }, 29 | 30 | { call: 'tokens.TST.balanceOf', args: [ctx.contracts.euler.address], equals: et.eth(20), }, 31 | 32 | // Now everybody's balance gets "rebased" up 1% 33 | 34 | { send: 'tokens.TST.setBalance', args: [ctx.wallet3.address, et.eth(10.1)], }, 35 | { send: 'tokens.TST.setBalance', args: [ctx.contracts.euler.address, et.eth(20.2)], }, 36 | 37 | { call: 'eTokens.eTST.balanceOfUnderlying', args: [ctx.wallet.address], equals: [et.eth(10.1), '0.10099999999999999'], }, 38 | { call: 'eTokens.eTST.balanceOfUnderlying', args: [ctx.wallet2.address], equals: [et.eth(10.1), '0.10099999999999999'], }, 39 | { call: 'tokens.TST.balanceOf', args: [ctx.wallet3.address], equals: et.eth(10.1), }, 40 | 41 | { from: ctx.wallet, send: 'eTokens.eTST.withdraw', args: [0, et.MaxUint256], }, 42 | 43 | { call: 'eTokens.eTST.balanceOfUnderlying', args: [ctx.wallet.address], equals: 0, }, 44 | { call: 'tokens.TST.balanceOf', args: [ctx.wallet.address], equals: [et.eth(10.1), '0.10099999999999999'], }, 45 | 46 | { call: 'tokens.TST.balanceOf', args: [ctx.contracts.euler.address], equals: [et.eth(10.1), '0.10099999999999999'], }, 47 | ], 48 | }) 49 | 50 | 51 | 52 | .run(); 53 | -------------------------------------------------------------------------------- /test/selfApproveEToken.js: -------------------------------------------------------------------------------- 1 | const et = require('./lib/eTestLib'); 2 | 3 | et.testSet({ 4 | desc: "self-approve eTokens", 5 | 6 | preActions: ctx => { 7 | let actions = []; 8 | 9 | for (let from of [ctx.wallet]) { 10 | actions.push({ from, send: 'tokens.TST.mint', args: [from.address, 1000], }); 11 | actions.push({ from, send: 'tokens.TST.approve', args: [ctx.contracts.euler.address, et.MaxUint256,], }); 12 | } 13 | 14 | return actions; 15 | }, 16 | }) 17 | 18 | 19 | .test({ 20 | desc: "self-approve with valid amount", 21 | actions: ctx => [ 22 | { send: 'eTokens.eTST.deposit', args: [0, 1000], }, 23 | 24 | { call: 'eTokens.eTST.balanceOf', args: [ctx.wallet.address], assertEql: 1000, }, 25 | 26 | { call: 'eTokens.eTST.allowance', args: [ctx.wallet.address, ctx.wallet.address], assertEql: 0, }, 27 | 28 | // revert on self-approve of eToken 29 | { from: ctx.wallet, send: 'eTokens.eTST.approve', args: [ctx.wallet.address, 10], expectError: 'e/self-approval', }, 30 | 31 | { call: 'eTokens.eTST.allowance', args: [ctx.wallet.address, ctx.wallet.address], assertEql: 0, }, 32 | ], 33 | }) 34 | 35 | 36 | .test({ 37 | desc: "self-approve with zero amount", 38 | actions: ctx => [ 39 | { send: 'eTokens.eTST.deposit', args: [0, 1000], }, 40 | 41 | { call: 'eTokens.eTST.balanceOf', args: [ctx.wallet.address], assertEql: 1000, }, 42 | 43 | { call: 'eTokens.eTST.allowance', args: [ctx.wallet.address, ctx.wallet.address], assertEql: 0, }, 44 | 45 | // revert on self-approve of eToken 46 | { from: ctx.wallet, send: 'eTokens.eTST.approve', args: [ctx.wallet.address, 0], expectError: 'e/self-approval', }, 47 | 48 | { call: 'eTokens.eTST.allowance', args: [ctx.wallet.address, ctx.wallet.address], assertEql: 0, }, 49 | ], 50 | }) 51 | 52 | 53 | .test({ 54 | desc: "self-approve with max amount exceeding balance", 55 | actions: ctx => [ 56 | { send: 'eTokens.eTST.deposit', args: [0, 1000], }, 57 | 58 | { call: 'eTokens.eTST.balanceOf', args: [ctx.wallet.address], assertEql: 1000, }, 59 | 60 | { call: 'eTokens.eTST.allowance', args: [ctx.wallet.address, ctx.wallet.address], assertEql: 0, }, 61 | 62 | // revert on self-approve of eToken 63 | { from: ctx.wallet, send: 'eTokens.eTST.approve', args: [ctx.wallet.address, et.MaxUint256], expectError: 'e/self-approval', }, 64 | 65 | { call: 'eTokens.eTST.allowance', args: [ctx.wallet.address, ctx.wallet.address], assertEql: 0, }, 66 | ], 67 | }) 68 | 69 | 70 | .test({ 71 | desc: "self-approve for subAccount with valid amount", 72 | actions: ctx => [ 73 | { send: 'eTokens.eTST.deposit', args: [0, 1000], }, 74 | 75 | { call: 'eTokens.eTST.balanceOf', args: [ctx.wallet.address], assertEql: 1000, }, 76 | 77 | { call: 'eTokens.eTST.allowance', args: [ctx.wallet.address, et.getSubAccount(ctx.wallet.address, 1)], assertEql: 0, }, 78 | 79 | // revert on self-approve of eToken 80 | { from: ctx.wallet, send: 'eTokens.eTST.approve', args: [et.getSubAccount(ctx.wallet.address, 1), 10], expectError: 'e/self-approval', }, 81 | 82 | { call: 'eTokens.eTST.allowance', args: [ctx.wallet.address, ctx.wallet.address], assertEql: 0, }, 83 | ], 84 | }) 85 | 86 | 87 | .run(); -------------------------------------------------------------------------------- /test/storage.js: -------------------------------------------------------------------------------- 1 | const et = require('./lib/eTestLib'); 2 | 3 | 4 | async function main() { 5 | // To verify storage layouts are consistent across upgrades 6 | //let buildInfo = await hre.artifacts.getBuildInfo('contracts/Storage.sol:Storage'); 7 | //let storageInfo = buildInfo.output.contracts['contracts/Storage.sol'].Storage.storageLayout; 8 | //console.log(et.dumpObj(storageInfo)); 9 | 10 | // To inspect asm: 11 | //let buildInfo = await hre.artifacts.getBuildInfo('contracts/modules/DToken.sol:DToken'); 12 | //let asm = buildInfo.output.contracts['contracts/modules/DToken.sol'].DToken.evm.assembly; 13 | //console.log(asm); 14 | } 15 | 16 | main(); 17 | -------------------------------------------------------------------------------- /test/tokensMisc.js: -------------------------------------------------------------------------------- 1 | const et = require('./lib/eTestLib'); 2 | 3 | et.testSet({ 4 | desc: "tokens misc", 5 | }) 6 | 7 | 8 | .test({ 9 | desc: "names and symbols", 10 | actions: ctx => [ 11 | { call: 'eTokens.eTST.name', args: [], assertEql: 'Euler Pool: Test Token', }, 12 | { call: 'eTokens.eTST.symbol', args: [], assertEql: 'eTST', }, 13 | { call: 'dTokens.dTST.name', args: [], assertEql: 'Euler Debt: Test Token', }, 14 | { call: 'dTokens.dTST.symbol', args: [], assertEql: 'dTST', }, 15 | ], 16 | }) 17 | 18 | 19 | .test({ 20 | desc: "underlyingAsset", 21 | actions: ctx => [ 22 | { call: 'eTokens.eTST.underlyingAsset', args: [], assertEql: ctx.contracts.tokens.TST.address, }, 23 | { call: 'dTokens.dTST.underlyingAsset', args: [], assertEql: ctx.contracts.tokens.TST.address, }, 24 | ], 25 | }) 26 | 27 | 28 | .test({ 29 | desc: "initial supplies and balances", 30 | actions: ctx => [ 31 | // Total supply is the default reserves = 1e6 wei or et.eth('0.000000000001') 32 | { call: 'eTokens.eTST.totalSupply', args: [], assertEql: et.BN(et.DefaultReserve), }, 33 | { call: 'eTokens.eTST.totalSupplyUnderlying', args: [], assertEql: et.BN(et.DefaultReserve), }, 34 | { call: 'eTokens.eTST.balanceOf', args: [ctx.wallet.address], assertEql: et.eth(0), }, 35 | { call: 'eTokens.eTST.balanceOfUnderlying', args: [ctx.wallet.address], assertEql: et.eth(0), }, 36 | 37 | { call: 'dTokens.dTST.totalSupply', args: [], assertEql: et.eth(0), }, 38 | { call: 'dTokens.dTST.totalSupplyExact', args: [], assertEql: et.eth(0), }, 39 | { call: 'dTokens.dTST.balanceOf', args: [ctx.wallet.address], assertEql: et.eth(0), }, 40 | { call: 'dTokens.dTST.balanceOfExact', args: [ctx.wallet.address], assertEql: et.eth(0), }, 41 | ], 42 | }) 43 | 44 | 45 | 46 | .run(); 47 | -------------------------------------------------------------------------------- /test/twap.js: -------------------------------------------------------------------------------- 1 | const et = require('./lib/eTestLib'); 2 | 3 | et.testSet({ 4 | desc: "twap handling", 5 | }) 6 | 7 | 8 | .test({ 9 | desc: "prices round-trip", 10 | actions: ctx => [ 11 | { action: 'cb', cb: async () => { 12 | // Make sure we exercise both directions 13 | 14 | let wethAddr = ethers.BigNumber.from(ctx.contracts.tokens.WETH.address); 15 | let tstAddr = ethers.BigNumber.from(ctx.contracts.tokens.TST.address); 16 | let tst6Addr = ethers.BigNumber.from(ctx.contracts.tokens.TST6.address); 17 | 18 | et.assert(wethAddr.lt(tstAddr), "weth < tst"); 19 | et.assert(wethAddr.gt(tst6Addr), "weth > tst6"); 20 | }}, 21 | 22 | { action: 'updateUniswapPrice', pair: 'TST/WETH', price: '20', }, 23 | { action: 'getPrice', underlying: 'TST', onResult: r => et.equals(r.twap, 20, 0.01), }, 24 | { action: 'updateUniswapPrice', pair: 'TST/WETH', price: '21', }, 25 | { action: 'getPrice', underlying: 'TST', onResult: r => et.equals(r.twap, 21, 0.01), }, 26 | 27 | { action: 'updateUniswapPrice', pair: 'TST2/WETH', price: '0.03', }, 28 | { action: 'getPrice', underlying: 'TST2', onResult: r => et.equals(r.twap, 0.03, '0.00001'), }, 29 | 30 | { action: 'updateUniswapPrice', pair: 'TST6/WETH', price: '0.000021333', }, 31 | { action: 'getPrice', underlying: 'TST6', onResult: r => et.equals(r.twap, '0.000021333', '0.000000001'), }, 32 | 33 | { action: 'updateUniswapPrice', pair: 'TST6/WETH', price: '1.3242', }, 34 | { action: 'getPrice', underlying: 'TST6', onResult: r => et.equals(r.twap, 1.3242, 0.001), }, 35 | 36 | // unchanged from above 37 | { action: 'getPrice', underlying: 'TST', onResult: r => et.equals(r.twap, 21, 0.01), }, 38 | 39 | { action: 'updateUniswapPrice', pair: 'TST/WETH', price: '0.0000329', }, 40 | { action: 'getPrice', underlying: 'TST', onResult: r => et.equals(r.twap, '0.0000329', '0.0000001'), }, 41 | ], 42 | }) 43 | 44 | 45 | 46 | 47 | 48 | .run(); 49 | --------------------------------------------------------------------------------