├── .env.example ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ └── audit_bug_report.yml └── workflows │ └── unit.yaml ├── .gitignore ├── .gitmodules ├── .husky ├── pre-commit └── pre-push ├── .prettierrc.json ├── .solhint.json ├── .solhintignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── addresses.txt ├── broadcast └── Deploy.s.sol │ └── 1 │ ├── run-1739974912.json │ └── run-latest.json ├── contracts ├── core │ ├── CorkConfig.sol │ ├── ExchangeRateProvider.sol │ ├── ModuleCore.sol │ ├── ModuleState.sol │ ├── Psm.sol │ ├── StateView.sol │ ├── Vault.sol │ ├── Withdrawal.sol │ ├── assets │ │ ├── Asset.sol │ │ ├── AssetFactory.sol │ │ ├── ProtectedUnit.sol │ │ ├── ProtectedUnitFactory.sol │ │ └── ProtectedUnitRouter.sol │ ├── flash-swaps │ │ └── FlashSwapRouter.sol │ └── liquidators │ │ └── cow-protocol │ │ ├── ChildLiquidator.sol │ │ └── Liquidator.sol ├── dummy │ ├── DummyERCWithPermit.sol │ ├── DummyWETH.sol │ └── ERC1967Proxy.sol ├── interfaces │ ├── IAssetFactory.sol │ ├── IDsFlashSwapRouter.sol │ ├── IErrors.sol │ ├── IExchangeRateProvider.sol │ ├── IExpiry.sol │ ├── ILiquidator.sol │ ├── ILiquidatorRegistry.sol │ ├── IPSMcore.sol │ ├── IProtectedUnit.sol │ ├── IProtectedUnitFactory.sol │ ├── IProtectedUnitLiquidation.sol │ ├── IProtectedUnitRouter.sol │ ├── IRates.sol │ ├── IRepurchase.sol │ ├── IReserve.sol │ ├── IVault.sol │ ├── IVaultLiquidation.sol │ ├── IWithdrawal.sol │ ├── IWithdrawalRouter.sol │ ├── Init.sol │ ├── UniV4 │ │ └── IMinimalHook.sol │ ├── offchain-helpers │ │ └── ILpHelper.sol │ └── uniswap-v2 │ │ ├── RouterV1.sol │ │ ├── RouterV2.sol │ │ ├── callee.sol │ │ ├── factory.sol │ │ └── pair.sol ├── libraries │ ├── DepegSwapLib.sol │ ├── DsFlashSwap.sol │ ├── DsSwapperMathLib.sol │ ├── ERC │ │ ├── CustomERC20Permit.sol │ │ └── ICustomERC20Permit.sol │ ├── Guard.sol │ ├── LogExpMath.sol │ ├── LpSymbolParser.sol │ ├── LvAssetLib.sol │ ├── MathHelper.sol │ ├── NavCircuitBreaker.sol │ ├── Pair.sol │ ├── PeggedAssetLib.sol │ ├── PermitChecker.sol │ ├── ProtectedUnitMath.sol │ ├── PsmLib.sol │ ├── RedemptionAssetManagerLib.sol │ ├── ReturnDataSlotLib.sol │ ├── SignatureHelperLib.sol │ ├── State.sol │ ├── TransferHelper.sol │ ├── VaultBalancesLib.sol │ ├── VaultLib.sol │ ├── VaultPoolLib.sol │ └── uni-v2 │ │ └── UniswapV2Library.sol ├── offchain-helpers │ └── LpHelper.sol ├── readers │ └── PriceReader.sol └── tokens │ ├── CETH.sol │ ├── CST.sol │ └── CUSD.sol ├── docs └── pull_request_template.md ├── foundry.toml ├── hardhat.config.ts ├── ignition └── modules │ ├── core.ts │ ├── lib.ts │ └── uniV2.ts ├── package.json ├── remappings.txt ├── script ├── foundry-scripts │ ├── Mainnet │ │ ├── Deploy.s.sol │ │ ├── SetupMarkets.s.sol │ │ ├── SimulateFunctions.s.sol │ │ └── VerifyContracts.s.sol │ ├── Testnet │ │ └── Deploy.s.sol │ └── Utils │ │ ├── HookMiner.sol │ │ ├── Permit2Mock.sol │ │ └── Utils.s.sol └── hardhat-scripts │ └── deploy.ts ├── test ├── contracts │ ├── Asset.ts │ ├── AssetFactory.ts │ ├── CorkConfig.ts │ ├── FlashSwapRouter.ts │ ├── LvCore.ts │ ├── ModuleCore.ts │ └── PsmCore.ts ├── forge │ ├── CST.t.sol │ ├── FlashSwap.t.sol │ ├── Helper.sol │ ├── POC.t.sol │ ├── Rollover.t.sol │ ├── SetupTest.t.sol │ ├── SigUtils.sol │ ├── SwapperMath.t.sol │ ├── TestCorkConfig.t.sol │ ├── TestFlashSwapRouter.sol │ ├── TestModuleCore.sol │ ├── Vault.t.sol │ ├── integration │ │ ├── config │ │ │ ├── CircuitBreakerUpdate.t.sol │ │ │ ├── Liquidator.t.sol │ │ │ ├── RateUpdate.t.sol │ │ │ └── Whitelist.t.sol │ │ ├── liquidation │ │ │ ├── ProtectedUnitLiquidation.t.sol │ │ │ └── VaultLiquidation.t.sol │ │ ├── protectedUnit │ │ │ ├── LiquidityFunds.t.sol │ │ │ ├── ProtectedUnit.t.sol │ │ │ └── ProtectedUnitRouter.t.sol │ │ ├── psm │ │ │ ├── Psm.t.sol │ │ │ └── rateUpdate.t.sol │ │ ├── router │ │ │ ├── buy │ │ │ │ └── Buy.t.sol │ │ │ └── sell │ │ │ │ └── SellDs.t.sol │ │ └── vault │ │ │ ├── LiquidityFunds.t.sol │ │ │ └── deposit │ │ │ └── Deposit.t.sol │ ├── smoke │ │ └── Smoke.t.sol │ └── unit │ │ ├── Asset.t.sol │ │ ├── LpParser.t.sol │ │ ├── TransferHelper.t.sol │ │ ├── protectedUnitMath │ │ └── LiquidtyMath.t.sol │ │ ├── swapMath │ │ ├── arpMath.t.sol │ │ └── buyMath.t.sol │ │ ├── tokenNames │ │ └── TokenNames.t.sol │ │ └── vaultMath │ │ ├── MathHelper.t.sol │ │ └── navMath.sol ├── helper │ ├── TestHelper.ts │ └── ext-abi │ │ ├── foundry │ │ ├── uni-v2-factory.json │ │ └── uni-v2-router.json │ │ └── hardhat │ │ ├── uni-v2-factory.json │ │ └── uni-v2-router.json └── lib │ ├── MathHelper.ts │ └── SwapMath.ts ├── tsconfig.json └── yarn.lock /.env.example: -------------------------------------------------------------------------------- 1 | # if set to true, will fail if the weth address is not set(since most networks have WETH deployed) 2 | # if set to false and the weth address is not set, the protocol will deploy a WETH contract 3 | PRODUCTION=false 4 | # set to "true" to enable the gas report 5 | REPORT_GAS= 6 | # CoinMarketCap API key for getting gas price, you can safely ignore this if you don't want to get gas price 7 | CMC_API_KEY= 8 | # deployer private key, default to hardhat account 0 9 | PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 10 | # WETH address, fill this if you want to deploy the protocol to a network that already has WETH deployed(e.g mainnet ETH) 11 | WETH=0x3BFE8e8821995737187c0Dfb4F26064679EB7C7F 12 | 13 | # fee percentage for redeeming RA using DS + PA on PSM in wei. use https://eth-converter.com/ to convert percentage to wei 14 | # for 1 percent, insert 1 to the "Ether" field and copy the value of the "Wei" field 15 | # default to 0.1% 16 | PSM_BASE_REDEMPTION_FEE_PERCENTAGE=100000000000000000 17 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/audit_bug_report.yml: -------------------------------------------------------------------------------- 1 | name: "[Cantina]: bug_name" 2 | type: Bug 3 | description: "cantina bug report" 4 | body: 5 | - type: input 6 | attributes: 7 | label: link 8 | description: issue link 9 | placeholder: https://cantina.xyz/abdcdsef 10 | validations: 11 | required: true 12 | -------------------------------------------------------------------------------- /.github/workflows/unit.yaml: -------------------------------------------------------------------------------- 1 | name: Unit Test 2 | 3 | on: 4 | pull_request: 5 | types: 6 | - synchronize 7 | - opened 8 | - edited 9 | branches: 10 | - main 11 | - tmp-changes 12 | 13 | push: 14 | paths: 15 | - "contracts/**" 16 | - "test/**" 17 | branches: 18 | - main 19 | - tmp-changes 20 | 21 | jobs: 22 | forge-test: 23 | runs-on: ubuntu-latest 24 | name: Forge Unit Test 25 | steps: 26 | # Step 1: Checkout the repository 27 | - uses: actions/checkout@v3 28 | with: 29 | submodules: recursive 30 | 31 | # Step 2: Install Foundry 32 | - name: Install Foundry 33 | uses: foundry-rs/foundry-toolchain@v1 34 | 35 | # Step 3: Enforce formatting check 36 | - name: Check Solidity Formatting 37 | run: | 38 | forge fmt --check || (echo "Formatting issues detected! Please run 'forge fmt' locally to fix them." && exit 1) 39 | 40 | # Step 4: Run unit tests 41 | - name: Run Tests 42 | run: forge test -vvv 43 | 44 | # Step 5: Run snapshot tests (if applicable) 45 | - name: Run Snapshot 46 | run: forge snapshot 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | .DS_Store 10 | 11 | # Diagnostic reports (https://nodejs.org/api/report.html) 12 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 13 | 14 | # Runtime data 15 | pids 16 | *.pid 17 | *.seed 18 | *.pid.lock 19 | 20 | # Directory for instrumented libs generated by jscoverage/JSCover 21 | lib-cov 22 | 23 | # Coverage directory used by tools like istanbul 24 | coverage 25 | *.lcov 26 | 27 | # nyc test coverage 28 | .nyc_output 29 | 30 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 31 | .grunt 32 | 33 | # Bower dependency directory (https://bower.io/) 34 | bower_components 35 | 36 | # node-waf configuration 37 | .lock-wscript 38 | 39 | # Compiled binary addons (https://nodejs.org/api/addons.html) 40 | build/Release 41 | 42 | # Dependency directories 43 | node_modules/ 44 | jspm_packages/ 45 | 46 | # Snowpack dependency directory (https://snowpack.dev/) 47 | web_modules/ 48 | 49 | # TypeScript cache 50 | *.tsbuildinfo 51 | 52 | # Optional npm cache directory 53 | .npm 54 | 55 | # Optional eslint cache 56 | .eslintcache 57 | 58 | # Optional stylelint cache 59 | .stylelintcache 60 | 61 | # Microbundle cache 62 | .rpt2_cache/ 63 | .rts2_cache_cjs/ 64 | .rts2_cache_es/ 65 | .rts2_cache_umd/ 66 | 67 | # Optional REPL history 68 | .node_repl_history 69 | 70 | # Output of 'npm pack' 71 | *.tgz 72 | 73 | # Yarn Integrity file 74 | .yarn-integrity 75 | 76 | # dotenv environment variable files 77 | .env 78 | .env.development.local 79 | .env.test.local 80 | .env.production.local 81 | .env.local 82 | 83 | # parcel-bundler cache (https://parceljs.org/) 84 | .cache 85 | .parcel-cache 86 | 87 | # Next.js build output 88 | .next 89 | out 90 | 91 | # Nuxt.js build / generate output 92 | .nuxt 93 | dist 94 | 95 | # Gatsby files 96 | .cache/ 97 | # Comment in the public line in if your project uses Gatsby and not Next.js 98 | # https://nextjs.org/blog/next-9-1#public-directory-support 99 | # public 100 | 101 | # vuepress build output 102 | .vuepress/dist 103 | 104 | # vuepress v2.x temp and cache directory 105 | .temp 106 | .cache 107 | 108 | # Serverless directories 109 | .serverless/ 110 | 111 | # FuseBox cache 112 | .fusebox/ 113 | 114 | # DynamoDB Local files 115 | .dynamodb/ 116 | 117 | # TernJS port file 118 | .tern-port 119 | 120 | # Stores VSCode versions used for testing VSCode extensions 121 | .vscode-test 122 | 123 | # yarn v2 124 | .yarn/cache 125 | .yarn/unplugged 126 | .yarn/build-state.yml 127 | .yarn/install-state.gz 128 | .pnp.* 129 | 130 | node_modules 131 | .env 132 | 133 | # Hardhat files 134 | /cache 135 | /artifacts 136 | 137 | # TypeChain files 138 | /typechain 139 | /typechain-types 140 | 141 | # solidity-coverage files 142 | /coverage 143 | /coverage.json 144 | 145 | # Hardhat Ignition default folder for deployments against a local node 146 | ignition/deployments/chain-31337 147 | gas-report.json 148 | 149 | #Forge ciles 150 | /cache_forge 151 | 152 | /.idea/ 153 | 154 | /optimized-out/ -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/forge-std"] 2 | path = lib/forge-std 3 | url = https://github.com/foundry-rs/forge-std 4 | [submodule "lib/openzeppelin-contracts"] 5 | path = lib/openzeppelin-contracts 6 | url = https://github.com/openzeppelin/openzeppelin-contracts 7 | [submodule "lib/openzeppelin-contracts-upgradeable"] 8 | path = lib/openzeppelin-contracts-upgradeable 9 | url = https://github.com/openzeppelin/openzeppelin-contracts-upgradeable 10 | [submodule "lib/prb-math"] 11 | path = lib/prb-math 12 | url = https://github.com/PaulRBerg/prb-math 13 | [submodule "lib/BokkyPooBahsDateTimeLibrary"] 14 | path = lib/BokkyPooBahsDateTimeLibrary 15 | url = https://github.com/bokkypoobah/BokkyPooBahsDateTimeLibrary 16 | [submodule "lib/v2-core"] 17 | path = lib/v2-core 18 | url = https://github.com/Uniswap/v2-core 19 | [submodule "lib/v2-periphery"] 20 | path = lib/v2-periphery 21 | url = https://github.com/Uniswap/v2-periphery 22 | [submodule "lib/openzeppelin-foundry-upgrades"] 23 | path = lib/openzeppelin-foundry-upgrades 24 | url = https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades 25 | [submodule "lib/Cork-Hook"] 26 | path = lib/Cork-Hook 27 | url = https://github.com/Cork-Technology/Cork-Hook 28 | [submodule "lib/permit2"] 29 | path = lib/permit2 30 | url = https://github.com/Uniswap/permit2 31 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | echo "Checking Solidity formatting with forge fmt..." 2 | forge fmt --check || { 3 | echo "Formatting issues detected! Please run 'forge fmt' to fix them." 4 | exit 1 5 | } 6 | echo "Solidity formatting status : Passed" 7 | -------------------------------------------------------------------------------- /.husky/pre-push: -------------------------------------------------------------------------------- 1 | echo "Checking Solidity formatting with forge fmt..." 2 | forge fmt --check || { 3 | echo "Formatting issues detected! Please run 'forge fmt' to fix them." 4 | exit 1 5 | } 6 | echo "Solidity formatting status : Passed" 7 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "overrides": [ 3 | { 4 | "files": "*.ts", 5 | "options": { 6 | "printWidth": 80, 7 | "tabWidth": 2, 8 | "useTabs": false, 9 | "singleQuote": false, 10 | "bracketSpacing": true, 11 | "explicitTypes": "preserve" 12 | } 13 | } 14 | 15 | ] 16 | } -------------------------------------------------------------------------------- /.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solhint:recommended", 3 | "rules": { 4 | "func-visibility": ["error", { "ignoreConstructors": true }], 5 | "interface-starts-with-i": "warn", 6 | "duplicated-imports": "warn", 7 | "func-param-name-mixedcase": "warn", 8 | "modifier-name-mixedcase": "warn", 9 | "gas-calldata-parameters": "warn", 10 | "gas-increment-by-one": "warn", 11 | "gas-length-in-loops": "warn", 12 | "gas-struct-packing": "warn" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.solhintignore: -------------------------------------------------------------------------------- 1 | # directories 2 | **/artifacts 3 | **/node_modules 4 | **/out 5 | **/broadcast 6 | **/cache 7 | **/cahe/forge 8 | **/docs 9 | **/lib -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "solidity.compileUsingRemoteVersion": "v0.8.24+commit.e11b9ed9" 3 | } 4 | -------------------------------------------------------------------------------- /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 | 9 | Parameters 10 | 11 | Licensor: Cork Protocol Inc. 12 | 13 | Licensed Work: Cork-Technology/Depeg-swap 14 | The Licensed Work is (c) 2024 Cork Protocol Inc. 15 | 16 | Additional Use Grant: Any uses listed and defined at 17 | [https://corkfi.notion.site/Additional-Use-Grant-Change-Date-49d8dd6e6c9943c28e6a093fcfc18ab5?pvs=4](https://www.notion.so/Additional-Use-Grant-Change-Date-49d8dd6e6c9943c28e6a093fcfc18ab5?pvs=21) 18 | 19 | Change Date: The earlier of 2028-09-17 or a date specified at 20 | [https://corkfi.notion.site/Additional-Use-Grant-Change-Date-49d8dd6e6c9943c28e6a093fcfc18ab5?pvs=4](https://www.notion.so/Additional-Use-Grant-Change-Date-49d8dd6e6c9943c28e6a093fcfc18ab5?pvs=21) 21 | 22 | Change License: GNU General Public License v2.0 or later 23 | 24 | ----------------------------------------------------------------------------- 25 | 26 | 27 | Terms 28 | 29 | The Licensor hereby grants you the right to copy, modify, create derivative 30 | works, redistribute, and make non-production use of the Licensed Work. The 31 | Licensor may make an Additional Use Grant, above, permitting limited 32 | production use. 33 | 34 | Effective on the Change Date, or the fourth anniversary of the first publicly 35 | available distribution of a specific version of the Licensed Work under this 36 | License, whichever comes first, the Licensor hereby grants you rights under 37 | the terms of the Change License, and the rights granted in the paragraph 38 | above terminate. 39 | 40 | If your use of the Licensed Work does not comply with the requirements 41 | currently in effect as described in this License, you must purchase a 42 | commercial license from the Licensor, its affiliated entities, or authorized 43 | resellers, or you must refrain from using the Licensed Work. 44 | 45 | All copies of the original and modified Licensed Work, and derivative works 46 | of the Licensed Work, are subject to this License. This License applies 47 | separately for each version of the Licensed Work and the Change Date may vary 48 | for each version of the Licensed Work released by Licensor. 49 | 50 | You must conspicuously display this License on each original or modified copy 51 | of the Licensed Work. If you receive the Licensed Work in original or 52 | modified form from a third party, the terms and conditions set forth in this 53 | License apply to your use of that work. 54 | 55 | Any use of the Licensed Work in violation of this License will automatically 56 | terminate your rights under this License for the current and all other 57 | versions of the Licensed Work. 58 | 59 | This License does not grant you any right in any trademark or logo of 60 | Licensor or its affiliates (provided that you may use a trademark or logo of 61 | Licensor as expressly required by this License). 62 | 63 | TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON 64 | AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, 65 | EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF 66 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND 67 | TITLE. 68 | 69 | MariaDB hereby grants you permission to use this License’s text to license 70 | your works, and to refer to it using the trademark "Business Source License", 71 | as long as you comply with the Covenants of Licensor below. 72 | 73 | ----------------------------------------------------------------------------- 74 | 75 | 76 | Covenants of Licensor 77 | 78 | In consideration of the right to use this License’s text and the "Business 79 | Source License" name and trademark, Licensor covenants to MariaDB, and to all 80 | other recipients of the licensed work to be provided by Licensor: 81 | 82 | 1. To specify as the Change License the GPL Version 2.0 or any later version, 83 | or a license that is compatible with GPL Version 2.0 or a later version, 84 | where "compatible" means that software provided under the Change License can 85 | be included in a program with software provided under GPL Version 2.0 or a 86 | later version. Licensor may specify additional Change Licenses without 87 | limitation. 88 | 2. To either: (a) specify an additional grant of rights to use that does not 89 | impose any additional restriction on the right granted in this License, as 90 | the Additional Use Grant; or (b) insert the text "None". 91 | 3. To specify a Change Date. 92 | 4. Not to modify this License in any other way. 93 | 94 | ----------------------------------------------------------------------------- 95 | 96 | 97 | Notice 98 | 99 | The Business Source License (this document, or the "License") is not an Open 100 | Source license. However, the Licensed Work will eventually be made available 101 | under an Open Source License, as stated in this License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Depeg Swap V1 2 | 3 | This repository contains core smart contracts of Depeg Swaps. 4 | # Build 5 | 6 | Install required dependencies 7 | 8 | ```bash 9 | forge install 10 | ``` 11 | To build & compile all contracts for testing purposes run : 12 | 13 | ```bash 14 | forge build 15 | ``` 16 | 17 | ### Deployment Build 18 | For production you need to use the optimized build with IR compilation turned on by setting the `FOUNDRY_PROFILE` environment variable to `optimized`: 19 | ```bash 20 | FOUNDRY_PROFILE=optimized forge build 21 | ``` 22 | 23 | # Setup Kontrol 24 | 25 | To install kontrol use below commands : 26 | 27 | ```bash 28 | bash <(curl https://kframework.org/install): install kup package manager. 29 | kup install kontrol: install Kontrol. 30 | kup list kontrol: list available Kontrol versions. 31 | ``` 32 | 33 | # Tests 34 | 35 | To run test, use this command : 36 | 37 | ```bash 38 | forge test 39 | ``` 40 | 41 | To run Formal verification proofs, use below commands : 42 | 43 | ```bash 44 | export FOUNDRY_PROFILE=kontrol-properties 45 | kontrol build 46 | kontrol prove 47 | ``` -------------------------------------------------------------------------------- /addresses.txt: -------------------------------------------------------------------------------- 1 | 0xBa66992bE4816Cc3877dA86fA982A93a6948dde9 2 | 0x5df5398A42a4841385f19db3450f1Ebeed4D709E 3 | 0xBa66992bE4816Cc3877dA86fA982A93a6948dde9 -------------------------------------------------------------------------------- /contracts/core/ExchangeRateProvider.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.24; 3 | 4 | import {Id, Pair, PairLibrary} from "../libraries/Pair.sol"; 5 | import {IErrors} from "./../interfaces/IErrors.sol"; 6 | import {MathHelper} from "./../libraries/MathHelper.sol"; 7 | import {IExchangeRateProvider} from "./../interfaces/IExchangeRateProvider.sol"; 8 | 9 | /** 10 | * @title ExchangeRateProvider Contract 11 | * @author Cork Team 12 | * @notice Contract for managing exchange rate 13 | */ 14 | contract ExchangeRateProvider is IErrors, IExchangeRateProvider { 15 | using PairLibrary for Pair; 16 | 17 | address internal CONFIG; 18 | 19 | mapping(Id => uint256) internal exchangeRate; 20 | 21 | /** 22 | * @dev checks if caller is config contract or not 23 | */ 24 | function onlyConfig() internal { 25 | if (msg.sender != CONFIG) { 26 | revert IErrors.OnlyConfigAllowed(); 27 | } 28 | } 29 | 30 | constructor(address _config) { 31 | if (_config == address(0)) { 32 | revert IErrors.ZeroAddress(); 33 | } 34 | CONFIG = _config; 35 | } 36 | 37 | function rate() external view returns (uint256) { 38 | return 0; // For future use 39 | } 40 | 41 | function rate(Id id) external view returns (uint256) { 42 | return exchangeRate[id]; 43 | } 44 | 45 | /** 46 | * @notice updates the exchange rate of the pair 47 | * @param id the id of the pair 48 | * @param newRate the exchange rate of the DS, token that are non-rebasing MUST set this to 1e18, and rebasing tokens should set this to the current exchange rate in the market 49 | */ 50 | function setRate(Id id, uint256 newRate) external { 51 | onlyConfig(); 52 | 53 | exchangeRate[id] = newRate; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /contracts/core/ModuleState.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.24; 3 | 4 | import {Id} from "../libraries/Pair.sol"; 5 | import {State} from "../libraries/State.sol"; 6 | import {IErrors} from "./../interfaces/IErrors.sol"; 7 | import {PsmLibrary} from "../libraries/PsmLib.sol"; 8 | import {RouterState} from "./flash-swaps/FlashSwapRouter.sol"; 9 | import {ICorkHook} from "./../interfaces/UniV4/IMinimalHook.sol"; 10 | import {ILiquidatorRegistry} from "./../interfaces/ILiquidatorRegistry.sol"; 11 | import {ReentrancyGuardTransient} from "@openzeppelin/contracts/utils/ReentrancyGuardTransient.sol"; 12 | import {Withdrawal} from "./Withdrawal.sol"; 13 | import {CorkConfig} from "./CorkConfig.sol"; 14 | import {Pair} from "../libraries/Pair.sol"; 15 | 16 | /** 17 | * @title ModuleState Abstract Contract 18 | * @author Cork Team 19 | * @notice Abstract ModuleState contract for providing base for Modulecore contract 20 | */ 21 | abstract contract ModuleState is IErrors, ReentrancyGuardTransient { 22 | using PsmLibrary for State; 23 | 24 | mapping(Id => State) internal states; 25 | 26 | address internal SWAP_ASSET_FACTORY; 27 | 28 | address internal DS_FLASHSWAP_ROUTER; 29 | 30 | /// @dev in this case is uni v4 31 | address internal AMM_HOOK; 32 | 33 | address internal CONFIG; 34 | 35 | address internal WITHDRAWAL_CONTRACT; 36 | 37 | /** 38 | * @dev checks if caller is config contract or not 39 | */ 40 | function onlyConfig() internal view { 41 | if (msg.sender != CONFIG) { 42 | revert OnlyConfigAllowed(); 43 | } 44 | } 45 | 46 | function factory() external view returns (address) { 47 | return SWAP_ASSET_FACTORY; 48 | } 49 | 50 | function initializeModuleState( 51 | address _swapAssetFactory, 52 | address _ammHook, 53 | address _dsFlashSwapRouter, 54 | address _config 55 | ) internal { 56 | if ( 57 | _swapAssetFactory == address(0) || _ammHook == address(0) || _dsFlashSwapRouter == address(0) 58 | || _config == address(0) 59 | ) { 60 | revert ZeroAddress(); 61 | } 62 | 63 | SWAP_ASSET_FACTORY = _swapAssetFactory; 64 | DS_FLASHSWAP_ROUTER = _dsFlashSwapRouter; 65 | CONFIG = _config; 66 | AMM_HOOK = _ammHook; 67 | } 68 | 69 | function _setWithdrawalContract(address _withdrawalContract) internal { 70 | WITHDRAWAL_CONTRACT = _withdrawalContract; 71 | } 72 | 73 | function getRouterCore() public view returns (RouterState) { 74 | return RouterState(DS_FLASHSWAP_ROUTER); 75 | } 76 | 77 | function getAmmRouter() public view returns (ICorkHook) { 78 | return ICorkHook(AMM_HOOK); 79 | } 80 | 81 | function getWithdrawalContract() public view returns (Withdrawal) { 82 | return Withdrawal(WITHDRAWAL_CONTRACT); 83 | } 84 | 85 | function getTreasuryAddress() public view returns (address) { 86 | return CorkConfig(CONFIG).treasury(); 87 | } 88 | 89 | function onlyInitialized(Id id) internal view { 90 | if (!states[id].isInitialized()) { 91 | revert NotInitialized(); 92 | } 93 | } 94 | 95 | function PSMDepositNotPaused(Id id) internal view { 96 | if (states[id].psm.isDepositPaused) { 97 | revert PSMDepositPaused(); 98 | } 99 | } 100 | 101 | function onlyFlashSwapRouter() internal view { 102 | if (msg.sender != DS_FLASHSWAP_ROUTER) { 103 | revert OnlyFlashSwapRouterAllowed(); 104 | } 105 | } 106 | 107 | function PSMWithdrawalNotPaused(Id id) internal view { 108 | if (states[id].psm.isWithdrawalPaused) { 109 | revert PSMWithdrawalPaused(); 110 | } 111 | } 112 | 113 | function PSMRepurchaseNotPaused(Id id) internal view { 114 | if (states[id].psm.isRepurchasePaused) { 115 | revert PSMRepurchasePaused(); 116 | } 117 | } 118 | 119 | function LVDepositNotPaused(Id id) internal view { 120 | if (states[id].vault.config.isDepositPaused) { 121 | revert LVDepositPaused(); 122 | } 123 | } 124 | 125 | function LVWithdrawalNotPaused(Id id) internal view { 126 | if (states[id].vault.config.isWithdrawalPaused) { 127 | revert LVWithdrawalPaused(); 128 | } 129 | } 130 | 131 | function onlyWhiteListedLiquidationContract() internal view { 132 | if (!ILiquidatorRegistry(CONFIG).isLiquidationWhitelisted(msg.sender)) { 133 | revert OnlyWhiteListed(); 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /contracts/core/StateView.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.24; 3 | 4 | import {ModuleState} from "./ModuleState.sol"; 5 | import {Id, Pair} from "./../libraries/Pair.sol"; 6 | import {DepegSwap} from "./../libraries/DepegSwapLib.sol"; 7 | import { 8 | VaultAmmLiquidityPool, Balances, VaultConfig, VaultBalances, VaultWithdrawalPool 9 | } from "./../libraries/State.sol"; 10 | import {LvAsset} from "./../libraries/LvAssetLib.sol"; 11 | import {BitMaps} from "@openzeppelin/contracts/utils/structs/BitMaps.sol"; 12 | 13 | /// @title StateView Contract, used for providing getter functions for ModuleCore contract 14 | /// intended usage is to run a local fork of blockchain and replace the bytecode of ModuleCore contract with this contract 15 | contract StateView is ModuleState { 16 | using BitMaps for BitMaps.BitMap; 17 | 18 | // ------------------------------ GLOBAL GETTERS ------------------------------ 19 | function getDsId(Id id) external view returns (uint256) { 20 | return states[id].globalAssetIdx; 21 | } 22 | 23 | function getPairInfo(Id id) external view returns (Pair memory) { 24 | return states[id].info; 25 | } 26 | 27 | function getDs(Id id, uint256 dsId) external view returns (DepegSwap memory) { 28 | return states[id].ds[dsId]; 29 | } 30 | //-------------------------------------------------------------------------------- 31 | 32 | // ------------------------------ PSM GETTERS ------------------------------ 33 | function getPsmBalances(Id id) external view returns (Balances memory) { 34 | return states[id].psm.balances; 35 | } 36 | 37 | function getPsmPoolArchiveRaAccrued(Id id, uint256 dsId) external view returns (uint256) { 38 | return states[id].psm.poolArchive[dsId].raAccrued; 39 | } 40 | 41 | function getPsmPoolArchivePaAccrued(Id id, uint256 dsId) external view returns (uint256) { 42 | return states[id].psm.poolArchive[dsId].paAccrued; 43 | } 44 | 45 | function getPsmPoolArchiveCtAttributed(Id id, uint256 dsId) external view returns (uint256) { 46 | return states[id].psm.poolArchive[dsId].ctAttributed; 47 | } 48 | 49 | function getPsmPoolArchiveAttributedToRollover(Id id, uint256 dsId) external view returns (uint256) { 50 | return states[id].psm.poolArchive[dsId].attributedToRolloverProfit; 51 | } 52 | 53 | function getPsmPoolArchiveRolloverClaims(Id id, uint256 dsId, address user) external view returns (uint256) { 54 | return states[id].psm.poolArchive[dsId].rolloverClaims[user]; 55 | } 56 | 57 | function getPsmPoolArchiveRolloverProfit(Id id, uint256 dsId) external view returns (uint256) { 58 | return states[id].psm.poolArchive[dsId].rolloverProfit; 59 | } 60 | 61 | function getPsmRepurchaseFeePercentage(Id id) external view returns (uint256) { 62 | return states[id].psm.repurchaseFeePercentage; 63 | } 64 | 65 | function getPsmLiquiditySeparated(Id id, uint256 dsId) external view returns (bool) { 66 | return states[id].psm.liquiditySeparated.get(dsId); 67 | } 68 | 69 | function getPsmIsDepositPaused(Id id) external view returns (bool) { 70 | return states[id].psm.isDepositPaused; 71 | } 72 | 73 | function getPsmIsWithdrawalPaused(Id id) external view returns (bool) { 74 | return states[id].psm.isWithdrawalPaused; 75 | } 76 | //-------------------------------------------------------------------------------- 77 | 78 | // ------------------------------ VAULT GETTERS ------------------------------ 79 | function getVaultBalances(Id id) external view returns (VaultBalances memory) { 80 | return states[id].vault.balances; 81 | } 82 | 83 | function getVaultConfig(Id id) external view returns (VaultConfig memory) { 84 | return states[id].vault.config; 85 | } 86 | 87 | function getVaultLvAsset(Id id) external view returns (LvAsset memory) { 88 | return states[id].vault.lv; 89 | } 90 | 91 | function getVaultLpLiquidated(Id id, uint256 dsId) external view returns (bool) { 92 | return states[id].vault.lpLiquidated.get(dsId); 93 | } 94 | 95 | function getVaultWithdrawalPool(Id id) external view returns (VaultWithdrawalPool memory) { 96 | return states[id].vault.pool.withdrawalPool; 97 | } 98 | 99 | function getVaultAmmLiquidityPool(Id id) external view returns (VaultAmmLiquidityPool memory) { 100 | return states[id].vault.pool.ammLiquidityPool; 101 | } 102 | 103 | function getWithdrawEligible(Id id, address user) external view returns (uint256) { 104 | return states[id].vault.pool.withdrawEligible[user]; 105 | } 106 | // -------------------------------------------------------------------------------- 107 | } 108 | -------------------------------------------------------------------------------- /contracts/core/Withdrawal.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.24; 3 | 4 | import {SafeERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 5 | import {IWithdrawalRouter} from "./../interfaces/IWithdrawalRouter.sol"; 6 | import {IWithdrawal} from "./../interfaces/IWithdrawal.sol"; 7 | import {ReentrancyGuardTransient} from "@openzeppelin/contracts/utils/ReentrancyGuardTransient.sol"; 8 | 9 | contract Withdrawal is ReentrancyGuardTransient, IWithdrawal { 10 | using SafeERC20 for IERC20; 11 | 12 | struct WithdrawalInfo { 13 | uint256 claimableAt; 14 | address owner; 15 | IWithdrawalRouter.Tokens[] tokens; 16 | } 17 | 18 | uint256 public constant DELAY = 3 days; 19 | 20 | address public immutable VAULT; 21 | 22 | mapping(bytes32 => WithdrawalInfo) internal withdrawals; 23 | 24 | // unique nonces to generate withdrawal id 25 | mapping(address => uint256) public nonces; 26 | 27 | constructor(address _vault) { 28 | if (_vault == address(0)) { 29 | revert ZeroAddress(); 30 | } 31 | VAULT = _vault; 32 | } 33 | 34 | modifier onlyVault() { 35 | if (msg.sender != VAULT) { 36 | revert OnlyVault(); 37 | } 38 | _; 39 | } 40 | 41 | modifier onlyOwner(bytes32 withdrawalId) { 42 | if (withdrawals[withdrawalId].owner != msg.sender) { 43 | revert NotOwner(withdrawals[withdrawalId].owner, msg.sender); 44 | } 45 | _; 46 | } 47 | 48 | modifier onlyWhenClaimable(bytes32 withdrawalId) { 49 | if (withdrawals[withdrawalId].claimableAt > block.timestamp) { 50 | revert NotYetClaimable(withdrawals[withdrawalId].claimableAt, block.timestamp); 51 | } 52 | _; 53 | } 54 | 55 | function getWithdrawal(bytes32 withdrawalId) external view returns (WithdrawalInfo memory) { 56 | return withdrawals[withdrawalId]; 57 | } 58 | 59 | // the token is expected to be transferred to this contract before calling this function 60 | function add(address owner, IWithdrawalRouter.Tokens[] calldata tokens) 61 | external 62 | onlyVault 63 | returns (bytes32 withdrawalId) 64 | { 65 | uint256 claimableAt = block.timestamp + DELAY; 66 | WithdrawalInfo memory withdrawal = WithdrawalInfo(claimableAt, owner, tokens); 67 | 68 | // solhint-disable-next-line gas-increment-by-one 69 | withdrawalId = keccak256(abi.encode(withdrawal, nonces[owner]++)); 70 | 71 | // copy withdrawal item 1-1 to memory 72 | WithdrawalInfo storage withdrawalStorageRef = withdrawals[withdrawalId]; 73 | 74 | withdrawalStorageRef.claimableAt = claimableAt; 75 | withdrawalStorageRef.owner = owner; 76 | 77 | // copy tokens data via a loop since direct memory copy isn't supported 78 | uint256 length = tokens.length; 79 | for (uint256 i = 0; i < length; ++i) { 80 | withdrawalStorageRef.tokens.push(tokens[i]); 81 | } 82 | 83 | emit WithdrawalRequested(withdrawalId, owner, claimableAt); 84 | } 85 | 86 | function claimToSelf(bytes32 withdrawalId) 87 | external 88 | nonReentrant 89 | onlyOwner(withdrawalId) 90 | onlyWhenClaimable(withdrawalId) 91 | { 92 | WithdrawalInfo storage withdrawal = withdrawals[withdrawalId]; 93 | 94 | uint256 length = withdrawal.tokens.length; 95 | for (uint256 i = 0; i < length; ++i) { 96 | IERC20(withdrawal.tokens[i].token).safeTransfer(withdrawal.owner, withdrawal.tokens[i].amount); 97 | } 98 | 99 | delete withdrawals[withdrawalId]; 100 | 101 | emit WithdrawalClaimed(withdrawalId, msg.sender); 102 | } 103 | 104 | function claimRouted(bytes32 withdrawalId, address router, bytes calldata routerData) 105 | external 106 | nonReentrant 107 | onlyOwner(withdrawalId) 108 | onlyWhenClaimable(withdrawalId) 109 | { 110 | WithdrawalInfo storage withdrawal = withdrawals[withdrawalId]; 111 | 112 | uint256 length = withdrawal.tokens.length; 113 | 114 | // transfer funds to router 115 | for (uint256 i = 0; i < length; ++i) { 116 | IERC20(withdrawal.tokens[i].token).safeTransfer(router, withdrawal.tokens[i].amount); 117 | } 118 | 119 | IWithdrawalRouter(router).route(address(this), withdrawals[withdrawalId].tokens, routerData); 120 | 121 | delete withdrawals[withdrawalId]; 122 | 123 | emit WithdrawalClaimed(withdrawalId, msg.sender); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /contracts/core/assets/Asset.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.24; 3 | 4 | import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; 5 | import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 6 | import {ERC20Burnable} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; 7 | import {IExpiry} from "../../interfaces/IExpiry.sol"; 8 | import {IRates} from "../../interfaces/IRates.sol"; 9 | import {CustomERC20Permit} from "../../libraries/ERC/CustomERC20Permit.sol"; 10 | import {ModuleCore} from "./../ModuleCore.sol"; 11 | import {IReserve} from "./../../interfaces/IReserve.sol"; 12 | import {Id} from "./../../libraries/Pair.sol"; 13 | /** 14 | * @title Contract for Adding Exchange Rate functionality 15 | * @author Cork Team 16 | * @notice Adds Exchange Rate functionality to Assets contracts 17 | */ 18 | 19 | abstract contract ExchangeRate is IRates { 20 | uint256 internal rate; 21 | 22 | constructor(uint256 _rate) { 23 | rate = _rate; 24 | } 25 | 26 | /** 27 | * @notice returns the current exchange rate 28 | */ 29 | function exchangeRate() external view override returns (uint256) { 30 | return rate; 31 | } 32 | } 33 | 34 | /** 35 | * @title Contract for Adding Expiry functionality to DS 36 | * @author Cork Team 37 | * @notice Adds Expiry functionality to Assets contracts 38 | * @dev Used for adding Expiry functionality to contracts like DS 39 | */ 40 | abstract contract Expiry is IExpiry { 41 | uint256 internal immutable EXPIRY; 42 | uint256 internal immutable ISSUED_AT; 43 | 44 | constructor(uint256 _expiry) { 45 | if (_expiry != 0 && _expiry < block.timestamp) { 46 | revert Expired(); 47 | } 48 | 49 | EXPIRY = _expiry; 50 | ISSUED_AT = block.timestamp; 51 | } 52 | 53 | /** 54 | * @notice returns if contract is expired or not(if timestamp==0 then contract not having any expiry) 55 | */ 56 | function isExpired() external view virtual returns (bool) { 57 | if (EXPIRY == 0) { 58 | return false; 59 | } 60 | 61 | return block.timestamp >= EXPIRY; 62 | } 63 | 64 | /** 65 | * @notice returns expiry timestamp of contract 66 | */ 67 | function expiry() external view virtual returns (uint256) { 68 | return EXPIRY; 69 | } 70 | 71 | function issuedAt() external view virtual returns (uint256) { 72 | return ISSUED_AT; 73 | } 74 | } 75 | 76 | /** 77 | * @title Assets Contract 78 | * @author Cork Team 79 | * @notice Contract for implementing assets like DS/CT etc 80 | */ 81 | contract Asset is ERC20Burnable, CustomERC20Permit, Ownable, Expiry, ExchangeRate, IReserve { 82 | uint256 internal immutable DS_ID; 83 | 84 | string public pairName; 85 | 86 | Id public marketId; 87 | 88 | ModuleCore public moduleCore; 89 | 90 | address public factory; 91 | 92 | modifier onlyFactory() { 93 | if (_msgSender() != factory) { 94 | revert OwnableUnauthorizedAccount(_msgSender()); 95 | } 96 | 97 | _; 98 | } 99 | 100 | constructor(string memory _pairName, address _owner, uint256 _expiry, uint256 _rate, uint256 _dsId) 101 | ExchangeRate(_rate) 102 | ERC20(_pairName, _pairName) 103 | CustomERC20Permit(_pairName) 104 | Ownable(_owner) 105 | Expiry(_expiry) 106 | { 107 | pairName = _pairName; 108 | DS_ID = _dsId; 109 | 110 | factory = _msgSender(); 111 | } 112 | 113 | function setMarketId(Id _marketId) external onlyFactory { 114 | marketId = _marketId; 115 | } 116 | 117 | function setModuleCore(address _moduleCore) external onlyFactory { 118 | moduleCore = ModuleCore(_moduleCore); 119 | } 120 | 121 | function getReserves() external view returns (uint256 ra, uint256 pa) { 122 | uint256 epoch = moduleCore.lastDsId(marketId); 123 | 124 | // will return the newest epoch reserve if the contract epoch is the same 125 | // as the one from module core 126 | // if the contract epoch is 0 means that this contract is an lv token 127 | // so we return the newest one also 128 | if (epoch == DS_ID || DS_ID == 0) { 129 | ra = moduleCore.valueLocked(marketId, true); 130 | pa = moduleCore.valueLocked(marketId, false); 131 | } else { 132 | ra = moduleCore.valueLocked(marketId, DS_ID, true); 133 | pa = moduleCore.valueLocked(marketId, DS_ID, false); 134 | } 135 | } 136 | 137 | /** 138 | * @notice mints `amount` number of tokens to `to` address 139 | * @param to address of receiver 140 | * @param amount number of tokens to be minted 141 | */ 142 | function mint(address to, uint256 amount) public onlyOwner { 143 | _mint(to, amount); 144 | } 145 | 146 | /** 147 | * @notice returns expiry timestamp of contract 148 | */ 149 | function dsId() external view virtual returns (uint256) { 150 | return DS_ID; 151 | } 152 | 153 | function updateRate(uint256 newRate) external override onlyOwner { 154 | rate = newRate; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /contracts/dummy/DummyERCWithPermit.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.24; 3 | 4 | import {ERC20, ERC20Burnable} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; 5 | import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol"; 6 | 7 | /** 8 | * @title DummyERCWithPermit Contract 9 | * @author Cork Team 10 | * @notice Dummy contract which provides ERC20 with Metadata for RA & PA 11 | */ 12 | contract DummyERCWithPermit is ERC20Burnable, ERC20Permit { 13 | event Deposit(address indexed dst, uint256 wad); 14 | event Withdrawal(address indexed src, uint256 wad); 15 | 16 | constructor(string memory name, string memory symbol) ERC20(name, symbol) ERC20Permit(name) {} 17 | 18 | function mint(address to, uint256 amount) external { 19 | _mint(to, amount); 20 | } 21 | 22 | function burnSelf(uint256 amount) external { 23 | _burn(msg.sender, amount); 24 | } 25 | 26 | function deposit() public payable { 27 | _mint(msg.sender, msg.value); 28 | emit Deposit(msg.sender, msg.value); 29 | } 30 | 31 | function withdraw(uint256 wad) public { 32 | _burn(msg.sender, wad); 33 | emit Withdrawal(msg.sender, wad); 34 | 35 | payable(msg.sender).transfer(wad); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /contracts/dummy/DummyWETH.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.24; 3 | 4 | import {ERC20, ERC20Burnable} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; 5 | 6 | /** 7 | * @title DummyWETH Contract 8 | * @author Cork Team 9 | * @notice Dummy contract which provides WETH with ERC20 10 | */ 11 | contract DummyWETH is ERC20Burnable { 12 | event Deposit(address indexed dst, uint256 wad); 13 | event Withdrawal(address indexed src, uint256 wad); 14 | 15 | constructor() ERC20("Dummy Wrapped ETH", "DWETH") {} 16 | 17 | fallback() external payable { 18 | deposit(); 19 | } 20 | 21 | receive() external payable { 22 | deposit(); 23 | } 24 | 25 | function deposit() public payable { 26 | _mint(msg.sender, msg.value); 27 | emit Deposit(msg.sender, msg.value); 28 | } 29 | 30 | function withdraw(uint256 wad) public { 31 | _burn(msg.sender, wad); 32 | emit Withdrawal(msg.sender, wad); 33 | 34 | payable(msg.sender).transfer(wad); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /contracts/dummy/ERC1967Proxy.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.24; 3 | 4 | import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; 5 | 6 | contract ERC1967ProxyImport { 7 | // This is just a placeholder contract to ensure ERC1967Proxy is compiled in hardhat 8 | // solhint-disable-next-line no-empty-blocks 9 | constructor() {} 10 | } 11 | -------------------------------------------------------------------------------- /contracts/interfaces/IAssetFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.24; 3 | 4 | import "./IErrors.sol"; 5 | 6 | /** 7 | * @title IAssetFactory Interface 8 | * @author Cork Team 9 | * @notice Interface for AssetsFactory contract 10 | */ 11 | interface IAssetFactory is IErrors { 12 | /// @notice emitted when a new CT + DS assets is deployed 13 | /// @param ra Address of RA(Redemption Asset) contract 14 | /// @param ct Address of CT(Cover Token) contract 15 | /// @param ds Address of DS(Depeg Swap Token) contract 16 | event AssetDeployed(address indexed ra, address indexed ct, address indexed ds); 17 | 18 | /// @notice emitted when a new LvAsset is deployed 19 | /// @param ra Address of RA(Redemption Asset) contract 20 | /// @param pa Address of PA(Pegged Asset) contract 21 | /// @param lv Address of LV(Liquidity Vault) contract 22 | event LvAssetDeployed(address indexed ra, address indexed pa, address indexed lv); 23 | 24 | /// @notice emitted when a module core is changed in asset factory 25 | /// @param oldModuleCore old module core address 26 | /// @param newModuleCore new module core address 27 | event ModuleCoreChanged(address oldModuleCore, address newModuleCore); 28 | 29 | /** 30 | * @notice for getting list of deployed Assets with this factory 31 | * @param page page number 32 | * @param limit number of entries per page 33 | * @return ra list of deployed RA assets 34 | * @return lv list of deployed LV assets 35 | */ 36 | function getDeployedAssets(uint8 page, uint8 limit) 37 | external 38 | view 39 | returns (address[] memory ra, address[] memory lv); 40 | 41 | /** 42 | * @notice for safety checks in psm core, also act as kind of like a registry 43 | * @param asset the address of Asset contract 44 | */ 45 | function isDeployed(address asset) external view returns (bool); 46 | 47 | /** 48 | * @notice for getting list of deployed SwapAssets with this factory 49 | * @param ra Address of RA 50 | * @param pa Address of PA 51 | * @param expiryInterval expiry interval 52 | * @param page page number 53 | * @param limit number of entries per page 54 | * @return ct list of deployed CT assets 55 | * @return ds list of deployed DS assets 56 | */ 57 | function getDeployedSwapAssets( 58 | address ra, 59 | address pa, 60 | uint256 initialArp, 61 | uint256 expiryInterval, 62 | address exchangeRateProvider, 63 | uint8 page, 64 | uint8 limit 65 | ) external view returns (address[] memory ct, address[] memory ds); 66 | 67 | struct DeployParams { 68 | address _ra; 69 | address _pa; 70 | address _owner; 71 | uint256 initialArp; 72 | uint256 expiryInterval; 73 | address exchangeRateProvider; 74 | uint256 psmExchangeRate; 75 | uint256 dsId; 76 | } 77 | 78 | function deploySwapAssets(DeployParams calldata params) external returns (address ct, address ds); 79 | 80 | /** 81 | * @notice deploys new LV Assets for given RA & PA 82 | * @param ra Address of RA 83 | * @param pa Address of PA 84 | * @param owner Address of asset owners 85 | * @return lv new LV contract address 86 | */ 87 | function deployLv( 88 | address ra, 89 | address pa, 90 | address owner, 91 | uint256 _initialArp, 92 | uint256 _expiryInterval, 93 | address _exchangeRateProvider 94 | ) external returns (address lv); 95 | 96 | function getLv(address _ra, address _pa, uint256 initialArp, uint256 expiryInterval, address exchangeRateProvider) 97 | external 98 | view 99 | returns (address); 100 | } 101 | -------------------------------------------------------------------------------- /contracts/interfaces/IExchangeRateProvider.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.24; 3 | 4 | import {Id} from "../libraries/Pair.sol"; 5 | 6 | /** 7 | * @title IExchangeRateProvider Interface 8 | * @author Cork Team 9 | * @notice Interface which provides exchange rate 10 | */ 11 | interface IExchangeRateProvider { 12 | function rate() external view returns (uint256); 13 | function rate(Id id) external view returns (uint256); 14 | } 15 | -------------------------------------------------------------------------------- /contracts/interfaces/IExpiry.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.24; 3 | 4 | import {IErrors} from "./IErrors.sol"; 5 | 6 | /** 7 | * @title IExpiry Interface 8 | * @author Cork Team 9 | * @notice IExpiry interface for Expiry contract 10 | */ 11 | interface IExpiry is IErrors { 12 | /// @notice returns true if the asset is expired 13 | function isExpired() external view returns (bool); 14 | 15 | ///@notice returns the expiry timestamp if 0 then it means it never expires 16 | function expiry() external view returns (uint256); 17 | 18 | ///@notice returns the timestamp when the asset was issued 19 | function issuedAt() external view returns (uint256); 20 | } 21 | -------------------------------------------------------------------------------- /contracts/interfaces/ILiquidatorRegistry.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.24; 3 | 4 | interface ILiquidatorRegistry { 5 | function isLiquidationWhitelisted(address liquidationAddress) external view returns (bool); 6 | 7 | function blacklist(address liquidationAddress) external; 8 | 9 | function whitelist(address liquidationAddress) external; 10 | } 11 | -------------------------------------------------------------------------------- /contracts/interfaces/IProtectedUnit.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.24; 3 | 4 | import {IErrors} from "./IErrors.sol"; 5 | 6 | /** 7 | * @title Protected Unit Interface 8 | * @notice Defines the standard functions and events for Protected Unit tokens 9 | * @dev Interface for creating, managing, and redeeming Protected Unit tokens 10 | */ 11 | interface IProtectedUnit is IErrors { 12 | /** 13 | * @notice Emmits when new Protected Unit tokens are created 14 | * @param minter The wallet address that created the tokens 15 | * @param amount How many tokens were created 16 | */ 17 | event Mint(address indexed minter, uint256 amount); 18 | 19 | /** 20 | * @notice Emmits when Protected Unit tokens are redeemed 21 | * @param dissolver The wallet address that redeemed the tokens 22 | * @param amount How many tokens were redeemed 23 | * @param dsAmount How many DS tokens were received 24 | * @param paAmount How many PA tokens were received 25 | */ 26 | event Burn(address indexed dissolver, uint256 amount, uint256 dsAmount, uint256 paAmount); 27 | 28 | /** 29 | * @notice Emmits when the maximum supply limit changes 30 | * @param newMintCap The new maximum supply limit 31 | */ 32 | event MintCapUpdated(uint256 newMintCap); 33 | 34 | /** 35 | * @notice Emmits when RA tokens are redeemed 36 | * @param redeemer The wallet address that performed the redemption 37 | * @param dsId The identifier of the DS token used 38 | * @param amount How many tokens were redeemed 39 | */ 40 | event RaRedeemed(address indexed redeemer, uint256 dsId, uint256 amount); 41 | 42 | /** 43 | * @notice Gets the maximum number of tokens that can be created 44 | * @return The current maximum supply limit 45 | */ 46 | function mintCap() external view returns (uint256); 47 | 48 | /** 49 | * @notice Calculates how many DS and PA tokens you need to create Protected Unit tokens 50 | * @param amount How many Protected Unit tokens you want to create 51 | * @return dsAmount How many DS tokens you need 52 | * @return paAmount How many PA tokens you need 53 | */ 54 | function previewMint(uint256 amount) external view returns (uint256 dsAmount, uint256 paAmount); 55 | 56 | /** 57 | * @notice Creates new Protected Unit tokens by depositing DS and PA tokens 58 | * @param amount How many Protected Unit tokens you want to create 59 | * @return dsAmount How many DS tokens were used 60 | * @return paAmount How many PA tokens were used 61 | */ 62 | function mint(uint256 amount) external returns (uint256 dsAmount, uint256 paAmount); 63 | 64 | /** 65 | * @notice Calculates how many tokens you'll receive for redeeming Protected Unit tokens 66 | * @param dissolver The wallet address that will redeem the tokens 67 | * @param amount How many Protected Unit tokens you want to redeem 68 | * @return dsAmount How many DS tokens you'll receive 69 | * @return paAmount How many PA tokens you'll receive 70 | * @return raAmount How many RA tokens you'll receive 71 | */ 72 | function previewBurn(address dissolver, uint256 amount) 73 | external 74 | view 75 | returns (uint256 dsAmount, uint256 paAmount, uint256 raAmount); 76 | 77 | /** 78 | * @notice Changes the maximum number of tokens that can be created 79 | * @param _newMintCap The new maximum supply limit 80 | * @dev Only callable by the contract owner 81 | */ 82 | function updateMintCap(uint256 _newMintCap) external; 83 | 84 | /** 85 | * @notice Gets the current balance of all tokens held by the contract 86 | * @return dsReserves How many DS tokens are in the contract 87 | * @return paReserves How many PA tokens are in the contract 88 | * @return raReserves How many RA tokens are in the contract 89 | */ 90 | function getReserves() external view returns (uint256 dsReserves, uint256 paReserves, uint256 raReserves); 91 | 92 | /** 93 | * @notice Updates the contract's internal record of token balances 94 | * @dev Call this to ensure the contract has accurate balance information 95 | */ 96 | function sync() external; 97 | } 98 | -------------------------------------------------------------------------------- /contracts/interfaces/IProtectedUnitFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.24; 3 | 4 | import {Id} from "../libraries/Pair.sol"; 5 | import {IErrors} from "./IErrors.sol"; 6 | 7 | /** 8 | * @title Protected Unit Factory Interface 9 | * @notice Defines functions for creating and managing Protected Unit contracts 10 | * @dev Interface for deploying and tracking Protected Unit tokens 11 | * @author Cork Protocol Team 12 | */ 13 | interface IProtectedUnitFactory is IErrors { 14 | /** 15 | * @notice Emmits when a new Protected Unit contract is created 16 | * @param pairId Unique identifier for the token pair 17 | * @param pa Address of the Protected Asset (PA) token 18 | * @param ra Address of the Return Asset (RA) token 19 | * @param protectedUnitAddress Address of the newly created Protected Unit contract 20 | */ 21 | event ProtectedUnitDeployed(Id indexed pairId, address pa, address ra, address indexed protectedUnitAddress); 22 | 23 | /** 24 | * @notice Creates a new Protected Unit contract 25 | * @dev Deploys a new ProtectedUnit and registers it in the factory's mappings 26 | * @param _id Unique Market/PSM/Vault ID from the ModuleCore contract 27 | * @param _pa Address of the Protected Asset token 28 | * @param _ra Address of the Return Asset token 29 | * @param _pairName Human-readable name for the token pair 30 | * @param _mintCap Maximum number of tokens that can be created 31 | * @return newUnit Address of the newly deployed Protected Unit contract 32 | * @custom:reverts ProtectedUnitExists if a Protected Unit with this ID already exists 33 | * @custom:reverts NotConfig if caller is not the CONFIG contract address 34 | * @custom:emits ProtectedUnitDeployed when a new contract is successfully deployed 35 | */ 36 | function deployProtectedUnit(Id _id, address _pa, address _ra, string calldata _pairName, uint256 _mintCap) 37 | external 38 | returns (address); 39 | 40 | /** 41 | * @notice Finds the address of an existing Protected Unit contract 42 | * @dev Simple lookup function to retrieve a Protected Unit contract by ID 43 | * @param _id The unique identifier of the Protected Unit to look up 44 | * @return The contract address of the Protected Unit (zero address if not found) 45 | */ 46 | function getProtectedUnitAddress(Id _id) external view returns (address); 47 | 48 | /** 49 | * @notice Gets a list of Protected Unit contracts created by this factory 50 | * @dev Uses pagination to handle large numbers of Protected Units efficiently 51 | * @param _page Which page of results to view (starts at 0) 52 | * @param _limit How many results to show per page 53 | * @return protectedUnitsList List of Protected Unit contract addresses for the requested page 54 | * @return idsList List of unique IDs for each Protected Unit in the response 55 | */ 56 | function getDeployedProtectedUnits(uint8 _page, uint8 _limit) 57 | external 58 | view 59 | returns (address[] memory protectedUnitsList, Id[] memory idsList); 60 | 61 | /** 62 | * @notice Removes a Protected Unit from the factory's registry 63 | * @dev Deletes the mapping entry for a Protected Unit by its ID 64 | * @param _id The unique identifier of the Protected Unit to remove 65 | * @custom:reverts NotConfig if caller is not the CONFIG address 66 | */ 67 | function deRegisterProtectedUnit(Id _id) external; 68 | } 69 | -------------------------------------------------------------------------------- /contracts/interfaces/IProtectedUnitLiquidation.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.24; 3 | 4 | import {IDsFlashSwapCore} from "./IDsFlashSwapRouter.sol"; 5 | 6 | /// @title Interface for the Protected Unit contract for liquidation 7 | /// @notice This contract is responsible for providing a way for liquidation contracts to request and send back funds 8 | /// IMPORTANT : the Protected Unit must make sure only authorized adddress can call the functions in this interface 9 | interface IProtectedUnitLiquidation { 10 | /// @notice Request funds for liquidation, will transfer the funds directly from the Protected Unit to the liquidation contract 11 | /// @param amount The amount of funds to request 12 | /// @param token The token to request, must be either RA or PA in the contract, will fail otherwise 13 | /// will revert if there's not enough funds in the Protected Unit 14 | /// IMPORTANT : the Protected Unit must make sure only whitelisted liquidation contract adddress can call this function 15 | function requestLiquidationFunds(uint256 amount, address token) external; 16 | 17 | /// @notice Receive funds from liquidation or leftover, the Protected Unit will do a transferFrom from the liquidation contract 18 | /// it is important to note that the Protected Unit will only transfer RA from the liquidation contract 19 | /// @param amount The amount of funds to receive 20 | /// @param token The token to receive, must be either RA or PA in the contract, will fail otherwise 21 | function receiveFunds(uint256 amount, address token) external; 22 | 23 | /// @notice Use funds from liquidation, the Protected Unit will use the received funds to buy DS 24 | /// IMPORTANT : the Protected Unit must make sure only the config contract can call this function, that in turns only can be called by the config contract manager 25 | function useFunds( 26 | uint256 amount, 27 | uint256 amountOutMin, 28 | IDsFlashSwapCore.BuyAprroxParams calldata params, 29 | IDsFlashSwapCore.OffchainGuess calldata offchainGuess 30 | ) external returns (uint256 amountOut); 31 | 32 | /// @notice Returns the amount of funds available for liquidation or trading 33 | /// @param token The token to check, must be either RA or PA in the contract, will fail otherwise 34 | /// it is important to note that the protected unit doesn't make any distinction between liquidation funds and funds that are not meant for liquidation 35 | /// it is the liquidator job to ensure that the funds are used for liquidation is being allocated correctly 36 | function fundsAvailable(address token) external view returns (uint256); 37 | 38 | /// @notice Event emitted when a liquidation contract requests funds 39 | event LiquidationFundsRequested(address indexed who, address token, uint256 amount); 40 | 41 | /// @notice Event emitted when a liquidation contract sends funds, can be both left over funds or resulting trade funds 42 | event FundsReceived(address indexed who, address token, uint256 amount); 43 | 44 | /// @notice Event emitted when the Protected Unit uses funds 45 | event FundsUsed(address indexed who, uint256 indexed dsId, uint256 raUsed, uint256 dsReceived); 46 | } 47 | -------------------------------------------------------------------------------- /contracts/interfaces/IProtectedUnitRouter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.24; 3 | 4 | import {IErrors} from "./IErrors.sol"; 5 | import {IPermit2} from "permit2/src/interfaces/IPermit2.sol"; 6 | import {ISignatureTransfer} from "permit2/src/interfaces/ISignatureTransfer.sol"; 7 | /** 8 | * @title Protected Unit Router Interface 9 | * @notice Defines functions for interacting with multiple Protected Units in a single transaction 10 | * @dev Interface for batch operations on Protected Unit tokens 11 | */ 12 | 13 | interface IProtectedUnitRouter is IErrors { 14 | /** 15 | * @notice Parameters needed for creating multiple Protected Unit tokens at once 16 | * @param protectedUnits List of Protected Unit contract addresses to mint from 17 | * @param amounts How many tokens to mint from each contract 18 | * @param permitBatchData Permission signatures for DS and PA token transfers 19 | * @param transferDetails Details of the token transfers 20 | * @param signature Signature authorizing the permits 21 | * @dev All arrays must be the same length 22 | */ 23 | struct BatchMintParams { 24 | address[] protectedUnits; 25 | uint256[] amounts; 26 | IPermit2.PermitBatchTransferFrom permitBatchData; 27 | IPermit2.SignatureTransferDetails[] transferDetails; 28 | bytes signature; 29 | } 30 | 31 | /** 32 | * @notice Parameters needed for burning multiple Protected Unit tokens 33 | * @param protectedUnits List of Protected Unit contract addresses to burn from 34 | * @param amounts How many tokens to burn from respective Protected Unit contract 35 | * @param permitBatchData Permission signatures for Protected Unit token transfers 36 | * @param transferDetails Details of the token transfers 37 | * @param signature Signature authorizing the permits 38 | * @dev All arrays must be the same length 39 | */ 40 | struct BatchBurnPermitParams { 41 | address[] protectedUnits; 42 | uint256[] amounts; 43 | IPermit2.PermitBatchTransferFrom permitBatchData; 44 | ISignatureTransfer.SignatureTransferDetails[] transferDetails; 45 | bytes signature; 46 | } 47 | 48 | /** 49 | * @notice Emmits when a new Protected Unit is added to the router 50 | * @param protectedUnit Address of the Protected Unit contract that was added 51 | */ 52 | event ProtectedUnitSet(address protectedUnit); 53 | 54 | /** 55 | * @notice Emmits when a Protected Unit is removed from the router 56 | * @param protectedUnit Address of the Protected Unit contract that was removed 57 | */ 58 | event ProtectedUnitRemoved(address protectedUnit); 59 | } 60 | -------------------------------------------------------------------------------- /contracts/interfaces/IRates.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.24; 3 | 4 | /** 5 | * @title IRates Interface 6 | * @author Cork Team 7 | * @notice IRates interface for providing excahngeRate functions 8 | */ 9 | interface IRates { 10 | /// @notice returns the exchange rate, if 0 then it means that there's no rate associated with it, like the case of LV token 11 | function exchangeRate() external view returns (uint256 rates); 12 | 13 | function updateRate(uint256 newRate) external; 14 | } 15 | -------------------------------------------------------------------------------- /contracts/interfaces/IRepurchase.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.24; 3 | 4 | import {Id} from "../libraries/Pair.sol"; 5 | import {IErrors} from "./IErrors.sol"; 6 | 7 | /** 8 | * @title IRepurchase Interface 9 | * @author Cork Team 10 | * @notice IRepurchase interface for supporting Repurchase features through PSMCore 11 | */ 12 | interface IRepurchase is IErrors { 13 | /** 14 | * @notice emitted when repurchase is done 15 | * @param id the id of PSM 16 | * @param buyer the address of the buyer 17 | * @param dsId the id of the DS 18 | * @param raUsed the amount of RA used 19 | * @param receivedPa the amount of PA received 20 | * @param receivedDs the amount of DS received 21 | * @param fee the fee charged 22 | * @param feePercentage the fee in percentage 23 | * @param exchangeRates the effective DS exchange rate at the time of repurchase 24 | */ 25 | event Repurchased( 26 | Id indexed id, 27 | address indexed buyer, 28 | uint256 indexed dsId, 29 | uint256 raUsed, 30 | uint256 receivedPa, 31 | uint256 receivedDs, 32 | uint256 feePercentage, 33 | uint256 fee, 34 | uint256 exchangeRates 35 | ); 36 | 37 | /// @notice Emitted when a repurchaseFee is updated for a given PSM 38 | /// @param id The PSM id 39 | /// @param repurchaseFeeRate The new repurchaseFee rate 40 | event RepurchaseFeeRateUpdated(Id indexed id, uint256 indexed repurchaseFeeRate); 41 | 42 | /** 43 | * @notice returns the fee percentage for repurchasing(1e18 = 1%) 44 | * @param id the id of PSM 45 | */ 46 | function repurchaseFee(Id id) external view returns (uint256); 47 | 48 | /** 49 | * @notice repurchase using RA 50 | * @param id the id of PSM 51 | * @param amount the amount of RA to use 52 | * @return dsId the id of the DS 53 | * @return receivedPa the amount of PA received 54 | * @return receivedDs the amount of DS received 55 | * @return feePercentage the fee in percentage 56 | * @return fee the fee charged 57 | * @return exchangeRates the effective DS exchange rate at the time of repurchase 58 | */ 59 | function repurchase(Id id, uint256 amount) 60 | external 61 | returns ( 62 | uint256 dsId, 63 | uint256 receivedPa, 64 | uint256 receivedDs, 65 | uint256 feePercentage, 66 | uint256 fee, 67 | uint256 exchangeRates 68 | ); 69 | 70 | /** 71 | * @notice return the amount of available PA and DS to purchase. 72 | * @param id the id of PSM 73 | * @return pa the amount of PA available 74 | * @return ds the amount of DS available 75 | * @return dsId the id of the DS available 76 | */ 77 | function availableForRepurchase(Id id) external view returns (uint256 pa, uint256 ds, uint256 dsId); 78 | 79 | /** 80 | * @notice returns the repurchase rates for a given DS 81 | * @param id the id of PSM 82 | */ 83 | function repurchaseRates(Id id) external view returns (uint256 rates); 84 | } 85 | -------------------------------------------------------------------------------- /contracts/interfaces/IReserve.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.26; 2 | 3 | interface IReserve { 4 | /// @notice return this pool reserve backing. 5 | /// note that in CT and DS this will return the associated ds id/epoch reserve 6 | /// e.g if the CT epoch/ds id is 2 but the newest ds id/epoch is 4 7 | /// this will still return backing reserve for ds id/epoch 2 8 | /// 9 | /// for LV tokens, this will always return the current backing reserve 10 | function getReserves() external view returns (uint256 ra, uint256 pa); 11 | } 12 | -------------------------------------------------------------------------------- /contracts/interfaces/IVaultLiquidation.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.24; 3 | 4 | import {Id} from "./../libraries/Pair.sol"; 5 | 6 | /// @title Interface for the VaultLiquidation contract 7 | /// @notice This contract is responsible for providing a way for liquidation contracts to request and send back funds 8 | /// IMPORTANT : the vault must make sure only authorized adddress can call the functions in this interface 9 | interface IVaultLiquidation { 10 | /// @notice Request funds for liquidation, will transfer the funds directly from the vault to the liquidation contract 11 | /// @param id The id of the vault 12 | /// @param amount The amount of funds to request 13 | /// will revert if there's not enough funds in the vault 14 | /// IMPORTANT : the vault must make sure only whitelisted liquidation contract adddress can call this function 15 | function requestLiquidationFunds(Id id, uint256 amount) external; 16 | 17 | /// @notice Receive funds from liquidation, the vault will do a transferFrom from the liquidation contract 18 | /// it is important to note that the vault will only transfer RA from the liquidation contract 19 | /// @param id The id of the vault 20 | /// @param amount The amount of funds to receive 21 | function receiveTradeExecuctionResultFunds(Id id, uint256 amount) external; 22 | 23 | /// @notice Use funds from liquidation, the vault will use the received funds to provide liquidity 24 | /// @param id The id of the vault 25 | /// IMPORTANT : the vault must make sure only the config contract can call this function, that in turns only can be called by the config contract manager 26 | function useTradeExecutionResultFunds(Id id) external; 27 | 28 | /// @notice Receive leftover funds from liquidation, the vault will do a transferFrom from the liquidation contract 29 | /// it is important to note that the vault will only transfer PA from the liquidation contract 30 | /// @param id The id of the vault 31 | /// @param amount The amount of funds to receive 32 | function receiveLeftoverFunds(Id id, uint256 amount) external; 33 | 34 | /// @notice Returns the amount of funds available for liquidation 35 | /// @param id The id of the vault 36 | function liquidationFundsAvailable(Id id) external view returns (uint256); 37 | 38 | /// @notice Returns the amount of RA that vault has received through liquidation 39 | /// @param id The id of the vault 40 | function tradeExecutionFundsAvailable(Id id) external view returns (uint256); 41 | 42 | /// @notice Event emitted when a liquidation contract requests funds 43 | event LiquidationFundsRequested(Id indexed id, address indexed who, uint256 amount); 44 | 45 | /// @notice Event emitted when a liquidation contract sends funds 46 | event TradeExecutionResultFundsReceived(Id indexed id, address indexed who, uint256 amount); 47 | 48 | /// @notice Event emitted when the vault uses funds 49 | event TradeExecutionResultFundsUsed(Id indexed id, address indexed who, uint256 amount); 50 | } 51 | -------------------------------------------------------------------------------- /contracts/interfaces/IWithdrawal.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.24; 3 | 4 | import {IWithdrawalRouter} from "./IWithdrawalRouter.sol"; 5 | import {IErrors} from "./IErrors.sol"; 6 | 7 | interface IWithdrawal is IErrors { 8 | function add(address owner, IWithdrawalRouter.Tokens[] calldata tokens) external returns (bytes32 withdrawalId); 9 | 10 | function claimToSelf(bytes32 withdrawalId) external; 11 | 12 | function claimRouted(bytes32 withdrawalId, address router, bytes calldata routerData) external; 13 | 14 | event WithdrawalRequested(bytes32 indexed withdrawalId, address indexed owner, uint256 claimableAt); 15 | 16 | event WithdrawalClaimed(bytes32 indexed withdrawalId, address indexed owner); 17 | } 18 | -------------------------------------------------------------------------------- /contracts/interfaces/IWithdrawalRouter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.24; 3 | 4 | interface IWithdrawalRouter { 5 | struct Tokens { 6 | address token; 7 | uint256 amount; 8 | } 9 | 10 | function route(address receiver, Tokens[] calldata tokens, bytes calldata routerData) external; 11 | } 12 | -------------------------------------------------------------------------------- /contracts/interfaces/UniV4/IMinimalHook.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.24; 3 | 4 | import {IErrors} from "./../IErrors.sol"; 5 | import {MarketSnapshot} from "Cork-Hook/lib/MarketSnapshot.sol"; 6 | 7 | interface ICorkHook is IErrors { 8 | function swap(address ra, address ct, uint256 amountRaOut, uint256 amountCtOut, bytes calldata data) 9 | external 10 | returns (uint256 amountIn); 11 | function addLiquidity( 12 | address ra, 13 | address ct, 14 | uint256 raAmount, 15 | uint256 ctAmount, 16 | uint256 amountRamin, 17 | uint256 amountCtmin, 18 | uint256 deadline 19 | ) external returns (uint256 amountRa, uint256 amountCt, uint256 mintedLp); 20 | 21 | function removeLiquidity( 22 | address ra, 23 | address ct, 24 | uint256 liquidityAmount, 25 | uint256 amountRamin, 26 | uint256 amountCtmin, 27 | uint256 deadline 28 | ) external returns (uint256 amountRa, uint256 amountCt); 29 | 30 | function getLiquidityToken(address ra, address ct) external view returns (address); 31 | 32 | function getReserves(address ra, address ct) external view returns (uint256, uint256); 33 | 34 | function getFee(address ra, address ct) 35 | external 36 | view 37 | returns (uint256 baseFeePercentage, uint256 actualFeePercentage); 38 | 39 | function getAmountIn(address ra, address ct, bool zeroForOne, uint256 amountOut) 40 | external 41 | view 42 | returns (uint256 amountIn); 43 | 44 | function getAmountOut(address ra, address ct, bool zeroForOne, uint256 amountIn) 45 | external 46 | view 47 | returns (uint256 amountOut); 48 | 49 | function getPoolManager() external view returns (address); 50 | 51 | function getForwarder() external view returns (address); 52 | 53 | function getMarketSnapshot(address ra, address ct) external view returns (MarketSnapshot memory); 54 | } 55 | -------------------------------------------------------------------------------- /contracts/interfaces/offchain-helpers/ILpHelper.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.24; 3 | 4 | import {ICorkHook} from "../UniV4/IMinimalHook.sol"; 5 | import {Id} from "./../../libraries/Pair.sol"; 6 | import {ModuleCore} from "./../../core/ModuleCore.sol"; 7 | import {IErrors} from "./../IErrors.sol"; 8 | 9 | /// @title ILpHelper 10 | /// @notice Interface for the LpHelper contract that provides utility functions for liquidity token operations 11 | interface ILpHelper is IErrors { 12 | /// @notice Returns the hook contract address 13 | /// @return The ICorkHook instance 14 | function hook() external view returns (ICorkHook); 15 | 16 | /// @notice Returns the module core contract address 17 | /// @return The ModuleCore instance 18 | function moduleCore() external view returns (ModuleCore); 19 | 20 | /// @notice Get reserves for a given liquidity token address 21 | /// @param lp The liquidity token address 22 | /// @return raReserve The reserve amount for the RA token 23 | /// @return ctReserve The reserve amount for the CT token 24 | function getReserve(address lp) external view returns (uint256 raReserve, uint256 ctReserve); 25 | 26 | /// @notice Get reserves for a given market ID using the latest dsId/epoch 27 | /// @param id The market ID 28 | /// @return raReserve The reserve amount for the RA token 29 | /// @return ctReserve The reserve amount for the CT token 30 | function getReserve(Id id) external view returns (uint256 raReserve, uint256 ctReserve); 31 | 32 | /// @notice Get reserves for a given market ID and dsId/epoch 33 | /// @param id The market ID 34 | /// @param dsId The dsId/epoch 35 | /// @return raReserve The reserve amount for the RA token 36 | /// @return ctReserve The reserve amount for the CT token 37 | function getReserve(Id id, uint256 dsId) external view returns (uint256 raReserve, uint256 ctReserve); 38 | 39 | /// @notice Get the liquidity token address for a given market ID using the latest dsId/epoch 40 | /// @param id The market ID 41 | /// @return liquidityToken The address of the liquidity token 42 | function getLpToken(Id id) external view returns (address liquidityToken); 43 | 44 | /// @notice Get the liquidity token address for a given market ID and dsId/epoch 45 | /// @param id The market ID 46 | /// @param dsId The dsId/epoch 47 | /// @return liquidityToken The address of the liquidity token 48 | function getLpToken(Id id, uint256 dsId) external view returns (address liquidityToken); 49 | } -------------------------------------------------------------------------------- /contracts/interfaces/uniswap-v2/RouterV1.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.24; 2 | 3 | interface IUniswapV2Router01 { 4 | function factory() external pure returns (address); 5 | 6 | function WETH() external pure returns (address); 7 | 8 | function addLiquidity( 9 | address tokenA, 10 | address tokenB, 11 | uint256 amountADesired, 12 | uint256 amountBDesired, 13 | uint256 amountAMin, 14 | uint256 amountBMin, 15 | address to, 16 | uint256 deadline 17 | ) external returns (uint256 amountA, uint256 amountB, uint256 liquidity); 18 | 19 | function addLiquidityETH( 20 | address token, 21 | uint256 amountTokenDesired, 22 | uint256 amountTokenMin, 23 | uint256 amountETHMin, 24 | address to, 25 | uint256 deadline 26 | ) external payable returns (uint256 amountToken, uint256 amountETH, uint256 liquidity); 27 | 28 | function removeLiquidity( 29 | address tokenA, 30 | address tokenB, 31 | uint256 liquidity, 32 | uint256 amountAMin, 33 | uint256 amountBMin, 34 | address to, 35 | uint256 deadline 36 | ) external returns (uint256 amountA, uint256 amountB); 37 | 38 | function removeLiquidityETH( 39 | address token, 40 | uint256 liquidity, 41 | uint256 amountTokenMin, 42 | uint256 amountETHMin, 43 | address to, 44 | uint256 deadline 45 | ) external returns (uint256 amountToken, uint256 amountETH); 46 | 47 | function removeLiquidityWithPermit( 48 | address tokenA, 49 | address tokenB, 50 | uint256 liquidity, 51 | uint256 amountAMin, 52 | uint256 amountBMin, 53 | address to, 54 | uint256 deadline, 55 | bool approveMax, 56 | uint8 v, 57 | bytes32 r, 58 | bytes32 s 59 | ) external returns (uint256 amountA, uint256 amountB); 60 | 61 | function removeLiquidityETHWithPermit( 62 | address token, 63 | uint256 liquidity, 64 | uint256 amountTokenMin, 65 | uint256 amountETHMin, 66 | address to, 67 | uint256 deadline, 68 | bool approveMax, 69 | uint8 v, 70 | bytes32 r, 71 | bytes32 s 72 | ) external returns (uint256 amountToken, uint256 amountETH); 73 | 74 | function swapExactTokensForTokens( 75 | uint256 amountIn, 76 | uint256 amountOutMin, 77 | address[] calldata path, 78 | address to, 79 | uint256 deadline 80 | ) external returns (uint256[] memory amounts); 81 | 82 | function swapTokensForExactTokens( 83 | uint256 amountOut, 84 | uint256 amountInMax, 85 | address[] calldata path, 86 | address to, 87 | uint256 deadline 88 | ) external returns (uint256[] memory amounts); 89 | 90 | function swapExactETHForTokens(uint256 amountOutMin, address[] calldata path, address to, uint256 deadline) 91 | external 92 | payable 93 | returns (uint256[] memory amounts); 94 | 95 | function swapTokensForExactETH( 96 | uint256 amountOut, 97 | uint256 amountInMax, 98 | address[] calldata path, 99 | address to, 100 | uint256 deadline 101 | ) external returns (uint256[] memory amounts); 102 | 103 | function swapExactTokensForETH( 104 | uint256 amountIn, 105 | uint256 amountOutMin, 106 | address[] calldata path, 107 | address to, 108 | uint256 deadline 109 | ) external returns (uint256[] memory amounts); 110 | 111 | function swapETHForExactTokens(uint256 amountOut, address[] calldata path, address to, uint256 deadline) 112 | external 113 | payable 114 | returns (uint256[] memory amounts); 115 | 116 | function quote(uint256 amountA, uint256 reserveA, uint256 reserveB) external pure returns (uint256 amountB); 117 | 118 | function getAmountOut(uint256 amountIn, uint256 reserveIn, uint256 reserveOut) 119 | external 120 | pure 121 | returns (uint256 amountOut); 122 | 123 | function getAmountIn(uint256 amountOut, uint256 reserveIn, uint256 reserveOut) 124 | external 125 | pure 126 | returns (uint256 amountIn); 127 | 128 | function getAmountsOut(uint256 amountIn, address[] calldata path) 129 | external 130 | view 131 | returns (uint256[] memory amounts); 132 | 133 | function getAmountsIn(uint256 amountOut, address[] calldata path) 134 | external 135 | view 136 | returns (uint256[] memory amounts); 137 | } 138 | -------------------------------------------------------------------------------- /contracts/interfaces/uniswap-v2/RouterV2.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.24; 2 | 3 | import {IUniswapV2Router01} from "./RouterV1.sol"; 4 | 5 | interface IUniswapV2Router02 is IUniswapV2Router01 { 6 | function removeLiquidityETHSupportingFeeOnTransferTokens( 7 | address token, 8 | uint256 liquidity, 9 | uint256 amountTokenMin, 10 | uint256 amountETHMin, 11 | address to, 12 | uint256 deadline 13 | ) external returns (uint256 amountETH); 14 | 15 | function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens( 16 | address token, 17 | uint256 liquidity, 18 | uint256 amountTokenMin, 19 | uint256 amountETHMin, 20 | address to, 21 | uint256 deadline, 22 | bool approveMax, 23 | uint8 v, 24 | bytes32 r, 25 | bytes32 s 26 | ) external returns (uint256 amountETH); 27 | 28 | function swapExactTokensForTokensSupportingFeeOnTransferTokens( 29 | uint256 amountIn, 30 | uint256 amountOutMin, 31 | address[] calldata path, 32 | address to, 33 | uint256 deadline 34 | ) external; 35 | function swapExactETHForTokensSupportingFeeOnTransferTokens( 36 | uint256 amountOutMin, 37 | address[] calldata path, 38 | address to, 39 | uint256 deadline 40 | ) external payable; 41 | function swapExactTokensForETHSupportingFeeOnTransferTokens( 42 | uint256 amountIn, 43 | uint256 amountOutMin, 44 | address[] calldata path, 45 | address to, 46 | uint256 deadline 47 | ) external; 48 | } 49 | -------------------------------------------------------------------------------- /contracts/interfaces/uniswap-v2/callee.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.24; 2 | 3 | interface IUniswapV2Callee { 4 | function uniswapV2Call(address sender, uint256 amount0, uint256 amount1, bytes calldata data) external; 5 | } 6 | -------------------------------------------------------------------------------- /contracts/interfaces/uniswap-v2/factory.sol: -------------------------------------------------------------------------------- 1 | // taken directly from forked repo 2 | pragma solidity ^0.8.24; 3 | 4 | interface IUniswapV2Factory { 5 | event PairCreated(address indexed token0, address indexed token1, address pair, uint256); 6 | 7 | function feeTo() external view returns (address); 8 | function feeToSetter() external view returns (address); 9 | 10 | function getPair(address tokenA, address tokenB) external view returns (address pair); 11 | function allPairs(uint256) external view returns (address pair); 12 | function allPairsLength() external view returns (uint256); 13 | 14 | function createPair(address tokenA, address tokenB) external returns (address pair); 15 | 16 | function setFeeTo(address) external; 17 | function setFeeToSetter(address) external; 18 | } 19 | -------------------------------------------------------------------------------- /contracts/interfaces/uniswap-v2/pair.sol: -------------------------------------------------------------------------------- 1 | // taken directly from forked repo 2 | pragma solidity ^0.8.24; 3 | 4 | interface IUniswapV2Pair { 5 | event Approval(address indexed owner, address indexed spender, uint256 value); 6 | event Transfer(address indexed from, address indexed to, uint256 value); 7 | 8 | function name() external pure returns (string memory); 9 | 10 | function symbol() external pure returns (string memory); 11 | 12 | function decimals() external pure returns (uint8); 13 | 14 | function totalSupply() external view returns (uint256); 15 | 16 | function balanceOf(address owner) external view returns (uint256); 17 | 18 | function allowance(address owner, address spender) external view returns (uint256); 19 | 20 | function approve(address spender, uint256 value) external returns (bool); 21 | 22 | function transfer(address to, uint256 value) external returns (bool); 23 | 24 | function transferFrom(address from, address to, uint256 value) external returns (bool); 25 | 26 | function DOMAIN_SEPARATOR() external view returns (bytes32); 27 | 28 | function PERMIT_TYPEHASH() external pure returns (bytes32); 29 | 30 | function nonces(address owner) external view returns (uint256); 31 | 32 | function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) 33 | external; 34 | 35 | event Mint(address indexed sender, uint256 amount0, uint256 amount1); 36 | event Burn(address indexed sender, uint256 amount0, uint256 amount1, address indexed to); 37 | event Swap( 38 | address indexed sender, 39 | uint256 amount0In, 40 | uint256 amount1In, 41 | uint256 amount0Out, 42 | uint256 amount1Out, 43 | address indexed to 44 | ); 45 | event Sync(uint112 reserve0, uint112 reserve1); 46 | 47 | function MINIMUM_LIQUIDITY() external pure returns (uint256); 48 | 49 | function factory() external view returns (address); 50 | 51 | function token0() external view returns (address); 52 | 53 | function token1() external view returns (address); 54 | 55 | function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); 56 | 57 | function price0CumulativeLast() external view returns (uint256); 58 | 59 | function price1CumulativeLast() external view returns (uint256); 60 | 61 | function kLast() external view returns (uint256); 62 | 63 | function mint(address to) external returns (uint256 liquidity); 64 | 65 | function burn(address to) external returns (uint256 amount0, uint256 amount1); 66 | 67 | function swap(uint256 amount0Out, uint256 amount1Out, address to, bytes calldata data) external; 68 | 69 | function skim(address to) external; 70 | 71 | function sync() external; 72 | 73 | function initialize(address, address, address) external; 74 | 75 | function dsFlashSwapRouter() external view returns (address); 76 | } 77 | -------------------------------------------------------------------------------- /contracts/libraries/DepegSwapLib.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.24; 3 | 4 | import {IERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol"; 5 | import {Asset} from "../core/assets/Asset.sol"; 6 | import {Signature, MinimalSignatureHelper} from "./SignatureHelperLib.sol"; 7 | 8 | /** 9 | * @dev DepegSwap structure for DS(DepegSwap) 10 | */ 11 | struct DepegSwap { 12 | bool expiredEventEmitted; 13 | address _address; 14 | address ct; 15 | uint256 ctRedeemed; 16 | } 17 | 18 | /** 19 | * @title DepegSwapLibrary Contract 20 | * @author Cork Team 21 | * @notice DepegSwapLibrary library which implements DepegSwap(DS) related features 22 | */ 23 | library DepegSwapLibrary { 24 | using MinimalSignatureHelper for Signature; 25 | 26 | /// @notice the exchange rate of DS can only go down at maximum 10% at a time 27 | uint256 internal constant MAX_RATE_DELTA_PERCENTAGE = 10e18; 28 | /// @notice Zero Address error, thrown when passed address is 0 29 | 30 | error ZeroAddress(); 31 | 32 | function isExpired(DepegSwap storage self) internal view returns (bool) { 33 | return Asset(self._address).isExpired(); 34 | } 35 | 36 | function isInitialized(DepegSwap storage self) internal view returns (bool) { 37 | return self._address != address(0) && self.ct != address(0); 38 | } 39 | 40 | function initialize(address _address, address ct) internal pure returns (DepegSwap memory) { 41 | if (_address == address(0) || ct == address(0)) { 42 | revert ZeroAddress(); 43 | } 44 | return DepegSwap({expiredEventEmitted: false, _address: _address, ct: ct, ctRedeemed: 0}); 45 | } 46 | 47 | function permitForRA( 48 | address contract_, 49 | bytes memory rawSig, 50 | address owner, 51 | address spender, 52 | uint256 value, 53 | uint256 deadline 54 | ) internal { 55 | // Split the raw signature 56 | Signature memory sig = MinimalSignatureHelper.split(rawSig); 57 | 58 | // Call the underlying ERC-20 contract's permit function 59 | IERC20Permit(contract_).permit(owner, spender, value, deadline, sig.v, sig.r, sig.s); 60 | } 61 | 62 | function permit( 63 | address contract_, 64 | bytes memory rawSig, 65 | address owner, 66 | address spender, 67 | uint256 value, 68 | uint256 deadline, 69 | string memory functionName 70 | ) internal { 71 | // Split the raw signature 72 | Signature memory sig = MinimalSignatureHelper.split(rawSig); 73 | 74 | // Call the underlying ERC-20 contract's permit function 75 | Asset(contract_).permit(owner, spender, value, deadline, sig.v, sig.r, sig.s, functionName); 76 | } 77 | 78 | function issue(DepegSwap memory self, address to, uint256 amount) internal { 79 | Asset(self._address).mint(to, amount); 80 | Asset(self.ct).mint(to, amount); 81 | } 82 | 83 | function burnCtSelf(DepegSwap storage self, uint256 amount) internal { 84 | Asset(self.ct).burn(amount); 85 | } 86 | 87 | function updateExchangeRate(DepegSwap storage self, uint256 rate) internal { 88 | Asset(self._address).updateRate(rate); 89 | Asset(self.ct).updateRate(rate); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /contracts/libraries/ERC/CustomERC20Permit.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.24; 3 | 4 | import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 5 | import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; 6 | import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; 7 | import {Nonces} from "@openzeppelin/contracts/utils/Nonces.sol"; 8 | import {ICustomERC20Permit} from "./ICustomERC20Permit.sol"; 9 | 10 | /** 11 | * @dev Implementation of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in 12 | * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]. 13 | * 14 | * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by 15 | * presenting a message signed by the account. By not relying on `{IERC20-approve}`, the token holder account doesn't 16 | * need to send a transaction, and thus is not required to hold Ether at all. 17 | * 18 | * This is modified version of the original OpenZeppelin Contracts ERC20Permit.sol contract. 19 | */ 20 | abstract contract CustomERC20Permit is ERC20, ICustomERC20Permit, EIP712, Nonces { 21 | bytes32 private constant PERMIT_TYPEHASH = keccak256( 22 | "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline,bytes32 functionHash)" 23 | ); 24 | 25 | /** 26 | * @dev Permit deadline has expired. 27 | */ 28 | error ERC2612ExpiredSignature(uint256 deadline); 29 | 30 | /** 31 | * @dev Mismatched signature. 32 | */ 33 | error ERC2612InvalidSigner(address signer, address owner); 34 | 35 | /** 36 | * @dev Initializes the {EIP712} domain separator using the `name` parameter, and setting `version` to `"1"`. 37 | * 38 | * It's a good idea to use the same `name` that is defined as the ERC20 token name. 39 | */ 40 | constructor(string memory name) EIP712(name, "1") {} 41 | 42 | /** 43 | * @dev This function is modified to include an additional parameter `functionHash` which is used to compute the 44 | * function hash for the permit signature. 45 | */ 46 | function permit( 47 | address owner, 48 | address spender, 49 | uint256 value, 50 | uint256 deadline, 51 | uint8 v, 52 | bytes32 r, 53 | bytes32 s, 54 | string memory functionName // Additional parameter to include function name 55 | ) public { 56 | if (block.timestamp > deadline) { 57 | revert ERC2612ExpiredSignature(deadline); 58 | } 59 | 60 | // Compute the function hash from the string input 61 | bytes32 functionHashBytes = keccak256(abi.encodePacked(functionName)); 62 | bytes32 structHash = 63 | keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, _useNonce(owner), deadline, functionHashBytes)); 64 | 65 | bytes32 hash = _hashTypedDataV4(structHash); 66 | 67 | address signer = ECDSA.recover(hash, v, r, s); 68 | if (signer != owner) { 69 | revert ERC2612InvalidSigner(signer, owner); 70 | } 71 | 72 | _approve(owner, spender, value); 73 | } 74 | 75 | /** 76 | * @inheritdoc ICustomERC20Permit 77 | */ 78 | function nonces(address owner) public view virtual override(ICustomERC20Permit, Nonces) returns (uint256) { 79 | return super.nonces(owner); 80 | } 81 | 82 | /** 83 | * @inheritdoc ICustomERC20Permit 84 | */ 85 | // solhint-disable-next-line func-name-mixedcase 86 | function DOMAIN_SEPARATOR() external view virtual returns (bytes32) { 87 | return _domainSeparatorV4(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /contracts/libraries/ERC/ICustomERC20Permit.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Permit.sol) 3 | 4 | pragma solidity ^0.8.24; 5 | 6 | /** 7 | * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in 8 | * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]. 9 | * 10 | * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by 11 | * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't 12 | * need to send a transaction, and thus is not required to hold Ether at all. 13 | * 14 | * ==== Security Considerations 15 | * 16 | * There are two important considerations concerning the use of `permit`. The first is that a valid permit signature 17 | * expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be 18 | * considered as an intention to spend the allowance in any specific way. The second is that because permits have 19 | * built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should 20 | * take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be 21 | * generally recommended is: 22 | * 23 | * ```solidity 24 | * function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public { 25 | * try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {} 26 | * doThing(..., value); 27 | * } 28 | * 29 | * function doThing(..., uint256 value) public { 30 | * token.safeTransferFrom(msg.sender, address(this), value); 31 | * ... 32 | * } 33 | * ``` 34 | * 35 | * Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of 36 | * `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also 37 | * {SafeERC20-safeTransferFrom}). 38 | * 39 | * Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so 40 | * contracts should have entry points that don't rely on permit. 41 | */ 42 | interface ICustomERC20Permit { 43 | /** 44 | * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens, 45 | * given ``owner``'s signed approval. 46 | * 47 | * IMPORTANT: The same issues {IERC20-approve} has related to transaction 48 | * ordering also apply here. 49 | * 50 | * Emits an {Approval} event. 51 | * 52 | * Requirements: 53 | * 54 | * - `spender` cannot be the zero address. 55 | * - `deadline` must be a timestamp in the future. 56 | * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` 57 | * over the EIP712-formatted function arguments. 58 | * - the signature must use ``owner``'s current nonce (see {nonces}). 59 | * 60 | * For more information on the signature format, see the 61 | * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP 62 | * section]. 63 | * 64 | * CAUTION: See Security Considerations above. 65 | */ 66 | function permit( 67 | address owner, 68 | address spender, 69 | uint256 value, 70 | uint256 deadline, 71 | uint8 v, 72 | bytes32 r, 73 | bytes32 s, 74 | string memory functionName 75 | ) external; 76 | 77 | /** 78 | * @dev Returns the current nonce for `owner`. This value must be 79 | * included whenever a signature is generated for {permit}. 80 | * 81 | * Every successful call to {permit} increases ``owner``'s nonce by one. This 82 | * prevents a signature from being used multiple times. 83 | */ 84 | function nonces(address owner) external view returns (uint256); 85 | 86 | /** 87 | * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}. 88 | */ 89 | // solhint-disable-next-line func-name-mixedcase 90 | function DOMAIN_SEPARATOR() external view returns (bytes32); 91 | } 92 | -------------------------------------------------------------------------------- /contracts/libraries/Guard.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.24; 3 | 4 | import {DepegSwap, DepegSwapLibrary} from "./DepegSwapLib.sol"; 5 | import {LvAsset, LvAssetLibrary} from "./LvAssetLib.sol"; 6 | 7 | /** 8 | * @title Guard Library Contract 9 | * @author Cork Team 10 | * @notice Guard library which implements modifiers for DS related features 11 | */ 12 | library Guard { 13 | using DepegSwapLibrary for DepegSwap; 14 | using LvAssetLibrary for LvAsset; 15 | 16 | /// @notice asset is expired 17 | error Expired(); 18 | 19 | /// @notice asset is not expired. e.g trying to redeem before expiry 20 | error NotExpired(); 21 | 22 | /// @notice asset is not initialized 23 | error Uninitialized(); 24 | 25 | function _onlyNotExpired(DepegSwap storage ds) internal view { 26 | if (ds.isExpired()) { 27 | revert Expired(); 28 | } 29 | } 30 | 31 | function _onlyExpired(DepegSwap storage ds) internal view { 32 | if (!ds.isExpired()) { 33 | revert NotExpired(); 34 | } 35 | } 36 | 37 | function _onlyInitialized(DepegSwap storage ds) internal view { 38 | if (!ds.isInitialized()) { 39 | revert Uninitialized(); 40 | } 41 | } 42 | 43 | function safeBeforeExpired(DepegSwap storage ds) internal view { 44 | _onlyInitialized(ds); 45 | _onlyNotExpired(ds); 46 | } 47 | 48 | function safeAfterExpired(DepegSwap storage ds) internal view { 49 | _onlyInitialized(ds); 50 | _onlyExpired(ds); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /contracts/libraries/LpSymbolParser.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.24; 3 | 4 | import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; 5 | 6 | library LpParser { 7 | /// @notice parses cork AMM liquidity token symbol to get the underlying pair 8 | /// @dev the LP token symbol is always on the format of 9 | /// LP-- that's ASCII encoded and address is a hex string that has the standard length of 42 10 | function parse(string memory symbol) internal pure returns (address ra, address ct) { 11 | string memory asciiRa = new string(42); 12 | string memory asciiCt = new string(42); 13 | 14 | // solhint-disable no-inline-assembly 15 | assembly ("memory-safe") { 16 | let symbolptr := add(symbol, 32) 17 | // the RA address pointer in LP- skipping "LP-" 18 | let raPtr := add(symbolptr, 3) 19 | // the CT address pointer in LP- skipping "LP--" 20 | let ctPtr := add(raPtr, 43) 21 | 22 | mcopy(add(asciiRa, 32), raPtr, 42) 23 | mcopy(add(asciiCt, 32), ctPtr, 42) 24 | } 25 | 26 | ra = Strings.parseAddress(asciiRa); 27 | ct = Strings.parseAddress(asciiCt); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /contracts/libraries/LvAssetLib.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.24; 3 | 4 | import {Asset} from "../core/assets/Asset.sol"; 5 | import {SafeERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 6 | 7 | /** 8 | * @dev LvAsset structure for Liquidity Vault Assets 9 | */ 10 | struct LvAsset { 11 | address _address; 12 | uint256 locked; 13 | } 14 | 15 | /** 16 | * @title LvAssetLibrary Contract 17 | * @author Cork Team 18 | * @notice LvAsset Library which implements features related to Lv(liquidity vault) Asset contract 19 | */ 20 | library LvAssetLibrary { 21 | using LvAssetLibrary for LvAsset; 22 | using SafeERC20 for IERC20; 23 | 24 | function initialize(address _address) internal pure returns (LvAsset memory) { 25 | return LvAsset(_address, 0); 26 | } 27 | 28 | function asErc20(LvAsset memory self) internal pure returns (IERC20) { 29 | return IERC20(self._address); 30 | } 31 | 32 | function isInitialized(LvAsset memory self) internal pure returns (bool) { 33 | return self._address != address(0); 34 | } 35 | 36 | function totalIssued(LvAsset memory self) internal view returns (uint256 total) { 37 | total = IERC20(self._address).totalSupply(); 38 | } 39 | 40 | function issue(LvAsset memory self, address to, uint256 amount) internal { 41 | Asset(self._address).mint(to, amount); 42 | } 43 | 44 | function incLocked(LvAsset storage self, uint256 amount) internal { 45 | self.locked = self.locked + amount; 46 | } 47 | 48 | function decLocked(LvAsset storage self, uint256 amount) internal { 49 | self.locked = self.locked - amount; 50 | } 51 | 52 | function lockFrom(LvAsset storage self, uint256 amount, address from) internal { 53 | incLocked(self, amount); 54 | lockUnchecked(self, amount, from); 55 | } 56 | 57 | function unlockTo(LvAsset storage self, uint256 amount, address to) internal { 58 | decLocked(self, amount); 59 | self.asErc20().safeTransfer(to, amount); 60 | } 61 | 62 | function lockUnchecked(LvAsset storage self, uint256 amount, address from) internal { 63 | self.asErc20().safeTransferFrom(from, address(this), amount); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /contracts/libraries/NavCircuitBreaker.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.24; 3 | 4 | import {State, NavCircuitBreaker} from "./State.sol"; 5 | import {IVault} from "./../interfaces/IVault.sol"; 6 | import {MathHelper} from "./MathHelper.sol"; 7 | import {IErrors} from "./../interfaces/IErrors.sol"; 8 | 9 | library NavCircuitBreakerLibrary { 10 | function _oldestSnapshotIndex(NavCircuitBreaker storage self) private view returns (uint256) { 11 | return self.lastUpdate0 <= self.lastUpdate1 ? 0 : 1; 12 | } 13 | 14 | function _updateSnapshot(NavCircuitBreaker storage self, uint256 currentNav) internal returns (bool) { 15 | uint256 oldestIndex = _oldestSnapshotIndex(self); 16 | 17 | if (oldestIndex == 0) { 18 | if (block.timestamp < self.lastUpdate0 + 1 days) return false; 19 | 20 | self.snapshot0 = currentNav; 21 | self.lastUpdate0 = block.timestamp; 22 | 23 | emit IVault.SnapshotUpdated(oldestIndex, currentNav); 24 | } else { 25 | if (block.timestamp < self.lastUpdate1 + 1 days) return false; 26 | 27 | self.snapshot1 = currentNav; 28 | self.lastUpdate1 = block.timestamp; 29 | 30 | emit IVault.SnapshotUpdated(oldestIndex, currentNav); 31 | } 32 | 33 | return true; 34 | } 35 | 36 | function _getReferenceNav(NavCircuitBreaker storage self) private view returns (uint256) { 37 | return self.snapshot0 > self.snapshot1 ? self.snapshot0 : self.snapshot1; 38 | } 39 | 40 | function validateAndUpdateDeposit(NavCircuitBreaker storage self, uint256 currentNav) internal { 41 | _updateSnapshot(self, currentNav); 42 | uint256 referenceNav = _getReferenceNav(self); 43 | uint256 delta = MathHelper.calculatePercentageFee(self.navThreshold, referenceNav); 44 | 45 | if (currentNav < delta) { 46 | revert IErrors.NavBelowThreshold(referenceNav, delta, currentNav); 47 | } 48 | } 49 | 50 | function updateOnWithdrawal(NavCircuitBreaker storage self, uint256 currentNav) internal returns (bool) { 51 | return _updateSnapshot(self, currentNav); 52 | } 53 | 54 | function forceUpdateSnapshot(NavCircuitBreaker storage self, uint256 currentNav) internal returns (bool) { 55 | return _updateSnapshot(self, currentNav); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /contracts/libraries/Pair.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.24; 3 | 4 | import {PeggedAsset, PeggedAssetLibrary} from "./PeggedAssetLib.sol"; 5 | 6 | type Id is bytes32; 7 | 8 | /** 9 | * @dev represent a RA/PA pair 10 | */ 11 | struct Pair { 12 | // pa/ct 13 | address pa; 14 | // ra/ds 15 | address ra; 16 | // initial arp 17 | uint256 initialArp; 18 | // expiry interval 19 | uint256 expiryInterval; 20 | // IExchangeRateProvider contract address 21 | address exchangeRateProvider; 22 | } 23 | 24 | /** 25 | * @title PairLibrary Contract 26 | * @author Cork Team 27 | * @notice Pair Library which implements functions for handling Pair operations 28 | */ 29 | library PairLibrary { 30 | using PeggedAssetLibrary for PeggedAsset; 31 | 32 | /// @notice Zero Address error, thrown when passed address is 0 33 | error ZeroAddress(); 34 | 35 | error InvalidAddress(); 36 | 37 | function toId(Pair memory key) internal pure returns (Id id) { 38 | id = Id.wrap(keccak256(abi.encode(key))); 39 | } 40 | 41 | function initalize(address pa, address ra, uint256 initialArp, uint256 expiry, address exchangeRateProvider) 42 | internal 43 | pure 44 | returns (Pair memory key) 45 | { 46 | if (pa == address(0) || ra == address(0)) { 47 | revert ZeroAddress(); 48 | } 49 | if (pa == ra) { 50 | revert InvalidAddress(); 51 | } 52 | key = Pair(pa, ra, initialArp, expiry, exchangeRateProvider); 53 | } 54 | 55 | function peggedAsset(Pair memory key) internal pure returns (PeggedAsset memory pa) { 56 | pa = PeggedAsset({_address: key.pa}); 57 | } 58 | 59 | function underlyingAsset(Pair memory key) internal pure returns (address ra, address pa) { 60 | pa = key.pa; 61 | ra = key.ra; 62 | } 63 | 64 | function redemptionAsset(Pair memory key) internal pure returns (address ra) { 65 | ra = key.ra; 66 | } 67 | 68 | function isInitialized(Pair memory key) internal pure returns (bool status) { 69 | status = key.pa != address(0) && key.ra != address(0); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /contracts/libraries/PeggedAssetLib.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.24; 3 | 4 | import {SafeERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 5 | 6 | /** 7 | * @dev PeggedAsset structure for PA(Pegged Assets) 8 | */ 9 | struct PeggedAsset { 10 | address _address; 11 | } 12 | 13 | /** 14 | * @title PeggedAssetLibrary Contract 15 | * @author Cork Team 16 | * @notice PeggedAsset Library which implements functions for Pegged assets 17 | */ 18 | library PeggedAssetLibrary { 19 | using PeggedAssetLibrary for PeggedAsset; 20 | using SafeERC20 for IERC20; 21 | 22 | function asErc20(PeggedAsset memory self) internal pure returns (IERC20) { 23 | return IERC20(self._address); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /contracts/libraries/PermitChecker.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.24; 3 | 4 | import {IERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol"; 5 | 6 | /** 7 | * @title PermitChecker Library Contract 8 | * @author Cork Team 9 | * @notice PermitChecker Library implements functions for checking if contract supports ERC20-Permit or not 10 | */ 11 | library PermitChecker { 12 | function supportsPermit(address token) internal view returns (bool) { 13 | return _hasNonces(token) && _hasDomainSeparator(token); 14 | } 15 | 16 | function _hasNonces(address token) internal view returns (bool) { 17 | try IERC20Permit(token).nonces(address(0)) returns (uint256) { 18 | return true; 19 | } catch { 20 | return false; 21 | } 22 | } 23 | 24 | function _hasDomainSeparator(address token) internal view returns (bool) { 25 | try IERC20Permit(token).DOMAIN_SEPARATOR() returns (bytes32) { 26 | return true; 27 | } catch { 28 | return false; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /contracts/libraries/ProtectedUnitMath.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.24; 3 | 4 | import {convert, intoUD60x18} from "@prb/math/src/SD59x18.sol"; 5 | import {UD60x18, convert, ud, add, mul, pow, sub, div, unwrap} from "@prb/math/src/UD60x18.sol"; 6 | import {IErrors} from "./../interfaces/IErrors.sol"; 7 | import {BuyMathBisectionSolver} from "./DsSwapperMathLib.sol"; 8 | import {TransferHelper} from "./TransferHelper.sol"; 9 | 10 | library ProtectedUnitMath { 11 | // caller of this contract must ensure the both amount is already proportional in amount! 12 | function mint(uint256 reservePa, uint256 totalLiquidity, uint256 amountPa, uint256 amountDs) 13 | internal 14 | pure 15 | returns (uint256 liquidityMinted) 16 | { 17 | // Calculate the liquidity tokens minted based on the added amounts and the current reserves 18 | // we mint 1:1 if total liquidity is 0, also enforce that the amount must be the same 19 | if (totalLiquidity == 0) { 20 | if (amountPa != amountDs) { 21 | revert IErrors.InvalidAmount(); 22 | } 23 | 24 | liquidityMinted = amountPa; 25 | } else { 26 | // Mint liquidity proportional to the added amounts 27 | liquidityMinted = unwrap(div(mul((ud(amountPa)), ud(totalLiquidity)), ud(reservePa))); 28 | } 29 | } 30 | 31 | function getProportionalAmount(uint256 amount0, uint256 reserve0, uint256 reserve1) 32 | internal 33 | pure 34 | returns (uint256 amount1) 35 | { 36 | return unwrap(div(mul(ud(amount0), ud(reserve1)), ud(reserve0))); 37 | } 38 | 39 | function previewMint(uint256 amount, uint256 paReserve, uint256 dsReserve, uint256 totalLiquidity) 40 | internal 41 | pure 42 | returns (uint256 dsAmount, uint256 paAmount) 43 | { 44 | if (totalLiquidity == 0) { 45 | return (amount, amount); 46 | } 47 | 48 | dsAmount = unwrap(mul(ud(amount), div(ud(dsReserve), ud(totalLiquidity)))); 49 | paAmount = unwrap(mul(ud(amount), div(ud(paReserve), ud(totalLiquidity)))); 50 | } 51 | 52 | function normalizeDecimals(uint256 amount, uint8 decimalsBefore, uint8 decimalsAfter) 53 | internal 54 | pure 55 | returns (uint256) 56 | { 57 | return TransferHelper.normalizeDecimals(amount, decimalsBefore, decimalsAfter); 58 | } 59 | 60 | function withdraw( 61 | uint256 reservePa, 62 | uint256 reserveDs, 63 | uint256 reserveRa, 64 | uint256 totalLiquidity, 65 | uint256 liquidityAmount 66 | ) internal pure returns (uint256 amountPa, uint256 amountDs, uint256 amountRa) { 67 | if (liquidityAmount <= 0) { 68 | revert IErrors.InvalidAmount(); 69 | } 70 | 71 | if (totalLiquidity <= 0) { 72 | revert IErrors.NotEnoughLiquidity(); 73 | } 74 | 75 | // Calculate the proportion of reserves to return based on the liquidity removed 76 | amountPa = unwrap(div(mul(ud(liquidityAmount), ud(reservePa)), ud(totalLiquidity))); 77 | 78 | amountDs = unwrap(div(mul(ud(liquidityAmount), ud(reserveDs)), ud(totalLiquidity))); 79 | 80 | amountRa = unwrap(div(mul(ud(liquidityAmount), ud(reserveRa)), ud(totalLiquidity))); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /contracts/libraries/RedemptionAssetManagerLib.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.24; 3 | 4 | import {Signature, MinimalSignatureHelper} from "./SignatureHelperLib.sol"; 5 | import {SafeERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 6 | 7 | /** 8 | * @dev RedemptionAssetManager structure for Redemption Manager 9 | */ 10 | struct RedemptionAssetManager { 11 | address _address; 12 | uint256 locked; 13 | uint256 free; 14 | } 15 | 16 | /** 17 | * @title RedemptionAssetManagerLibrary Contract 18 | * @author Cork Team 19 | * @notice RedemptionAssetManager Library implements functions for RA(Redemption Assets) contract 20 | */ 21 | library RedemptionAssetManagerLibrary { 22 | using MinimalSignatureHelper for Signature; 23 | using SafeERC20 for IERC20; 24 | 25 | function initialize(address ra) internal pure returns (RedemptionAssetManager memory) { 26 | return RedemptionAssetManager(ra, 0, 0); 27 | } 28 | 29 | function reset(RedemptionAssetManager storage self) internal { 30 | self.locked = 0; 31 | self.free = 0; 32 | } 33 | 34 | function incLocked(RedemptionAssetManager storage self, uint256 amount) internal { 35 | self.locked = self.locked + amount; 36 | } 37 | 38 | function convertAllToFree(RedemptionAssetManager storage self) internal returns (uint256) { 39 | if (self.locked == 0) { 40 | return self.free; 41 | } 42 | 43 | self.free = self.free + self.locked; 44 | self.locked = 0; 45 | 46 | return self.free; 47 | } 48 | 49 | function decLocked(RedemptionAssetManager storage self, uint256 amount) internal { 50 | self.locked = self.locked - amount; 51 | } 52 | 53 | function lockFrom(RedemptionAssetManager storage self, uint256 amount, address from) internal { 54 | incLocked(self, amount); 55 | lockUnchecked(self, amount, from); 56 | } 57 | 58 | function lockUnchecked(RedemptionAssetManager storage self, uint256 amount, address from) internal { 59 | IERC20(self._address).safeTransferFrom(from, address(this), amount); 60 | } 61 | 62 | function unlockTo(RedemptionAssetManager storage self, address to, uint256 amount) internal { 63 | decLocked(self, amount); 64 | unlockToUnchecked(self, amount, to); 65 | } 66 | 67 | function unlockToUnchecked(RedemptionAssetManager storage self, uint256 amount, address to) internal { 68 | IERC20(self._address).safeTransfer(to, amount); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /contracts/libraries/ReturnDataSlotLib.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.24; 3 | 4 | library ReturnDataSlotLib { 5 | // keccak256("SELL") 6 | bytes32 public constant RETURN_SLOT_SELL = 0x46e2aa85b4ea4837644e46fe22acb44743da12e349006c0093a61f6bf0967602; 7 | 8 | // keccak256("BUY") 9 | bytes32 public constant RETURN_SLOT_BUY = 0x8e9b148654316179bd45e9ec5f0b575ae8288e79df16d7be748ec9a9bdca8b4c; 10 | 11 | // keccak256("REFUNDED") 12 | bytes32 public constant REFUNDED_SLOT = 0x0ae202c5d1ff9dcd4329d24acbf3bddff6279ad182d19d899440adb36d927795; 13 | 14 | // keccak256("DS_FEE_AMOUNT") 15 | bytes32 public constant DS_FEE_AMOUNT = 0x2edcf68d3b1bfd48ba1b97a39acb4e9553bc609ae5ceef6b88a0581565dba754; 16 | 17 | function increase(bytes32 slot, uint256 _value) internal { 18 | uint256 prev = get(slot); 19 | 20 | set(slot, prev + _value); 21 | } 22 | 23 | function set(bytes32 slot, uint256 _value) private { 24 | assembly { 25 | tstore(slot, _value) 26 | } 27 | } 28 | 29 | function get(bytes32 slot) internal view returns (uint256 _value) { 30 | assembly { 31 | _value := tload(slot) 32 | } 33 | } 34 | 35 | function clear(bytes32 slot) internal { 36 | assembly { 37 | tstore(slot, 0) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /contracts/libraries/SignatureHelperLib.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.24; 3 | 4 | /** 5 | * @dev Signature structure 6 | */ 7 | struct Signature { 8 | uint8 v; 9 | bytes32 r; 10 | bytes32 s; 11 | } 12 | 13 | /** 14 | * @title MinimalSignatureHelper Library Contract 15 | * @author Cork Team 16 | * @notice MinimalSignatureHelper Library implements signature related functions 17 | */ 18 | library MinimalSignatureHelper { 19 | /// @notice thrown when Signature length is not valid 20 | error InvalidSignatureLength(uint256 length); 21 | 22 | function split(bytes memory raw) internal pure returns (Signature memory sig) { 23 | if (raw.length != 65) { 24 | revert InvalidSignatureLength(raw.length); 25 | } 26 | 27 | (uint8 v, bytes32 r, bytes32 s) = splitUnchecked(raw); 28 | 29 | sig = Signature({v: v, r: r, s: s}); 30 | } 31 | 32 | function splitUnchecked(bytes memory sig) private pure returns (uint8 v, bytes32 r, bytes32 s) { 33 | assembly { 34 | r := mload(add(sig, 32)) 35 | s := mload(add(sig, 64)) 36 | v := byte(0, mload(add(sig, 96))) 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /contracts/libraries/State.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.24; 3 | 4 | import {Pair} from "./Pair.sol"; 5 | import {RedemptionAssetManager} from "./RedemptionAssetManagerLib.sol"; 6 | import {LvAsset} from "./LvAssetLib.sol"; 7 | import {BitMaps} from "@openzeppelin/contracts/utils/structs/BitMaps.sol"; 8 | import {DepegSwap} from "./DepegSwapLib.sol"; 9 | 10 | /** 11 | * @dev State structure 12 | * @dev as there are some fields that are used in PSM but not in LV 13 | */ 14 | struct State { 15 | /// @dev used to track current ds and ct for both lv and psm 16 | uint256 globalAssetIdx; 17 | Pair info; 18 | /// @dev dsId => DepegSwap(CT + DS) 19 | mapping(uint256 => DepegSwap) ds; 20 | PsmState psm; 21 | VaultState vault; 22 | } 23 | 24 | /** 25 | * @dev PsmState structure for PSM Core 26 | */ 27 | struct PsmState { 28 | Balances balances; 29 | uint256 repurchaseFeePercentage; 30 | uint256 repurchaseFeeTreasurySplitPercentage; 31 | BitMaps.BitMap liquiditySeparated; 32 | /// @dev dsId => PsmPoolArchive 33 | mapping(uint256 => PsmPoolArchive) poolArchive; 34 | mapping(address => bool) autoSell; 35 | bool isDepositPaused; 36 | bool isWithdrawalPaused; 37 | bool isRepurchasePaused; 38 | uint256 psmBaseRedemptionFeePercentage; 39 | uint256 psmBaseFeeTreasurySplitPercentage; 40 | } 41 | 42 | /** 43 | * @dev PsmPoolArchive structure for PSM Pools 44 | */ 45 | struct PsmPoolArchive { 46 | uint256 raAccrued; 47 | uint256 paAccrued; 48 | uint256 ctAttributed; 49 | uint256 attributedToRolloverProfit; 50 | /// @dev user => amount 51 | mapping(address => uint256) rolloverClaims; 52 | uint256 rolloverProfit; 53 | } 54 | 55 | /** 56 | * @dev Balances structure for managing balances in PSM Core 57 | */ 58 | struct Balances { 59 | RedemptionAssetManager ra; 60 | uint256 dsBalance; 61 | uint256 paBalance; 62 | uint256 ctBalance; 63 | } 64 | 65 | /** 66 | * @dev Balances structure for managing balances in PSM Core 67 | */ 68 | struct VaultBalances { 69 | RedemptionAssetManager ra; 70 | uint256 ctBalance; 71 | uint256 lpBalance; 72 | } 73 | 74 | /** 75 | * @dev VaultPool structure for providing pools in Vault(Liquidity Pool) 76 | */ 77 | struct VaultPool { 78 | VaultWithdrawalPool withdrawalPool; 79 | VaultAmmLiquidityPool ammLiquidityPool; 80 | /// @dev user => (dsId => amount) 81 | mapping(address => uint256) withdrawEligible; 82 | } 83 | 84 | /** 85 | * @dev VaultWithdrawalPool structure for providing withdrawal pools in Vault(Liquidity Pool) 86 | */ 87 | struct VaultWithdrawalPool { 88 | uint256 atrributedLv; 89 | uint256 raExchangeRate; 90 | uint256 paExchangeRate; 91 | uint256 raBalance; 92 | uint256 paBalance; 93 | } 94 | 95 | /** 96 | * @dev VaultAmmLiquidityPool structure for providing AMM pools in Vault(Liquidity Pool) 97 | * This should only be used at the end of each epoch(dsId) lifecyle(e.g at expiry) to pool all RA to be used 98 | * as liquidity for initiating AMM in the next epoch 99 | */ 100 | struct VaultAmmLiquidityPool { 101 | uint256 balance; 102 | } 103 | 104 | /** 105 | * @dev VaultState structure for VaultCore 106 | */ 107 | struct VaultState { 108 | VaultBalances balances; 109 | VaultConfig config; 110 | LvAsset lv; 111 | BitMaps.BitMap lpLiquidated; 112 | VaultPool pool; 113 | // will be set to true after first deposit to LV. 114 | // to prevent manipulative behavior when depositing to Lv since we depend on preview redeem early to get 115 | // the correct exchange rate of LV 116 | bool initialized; 117 | /// @notice the percentage of which the RA that user deposit will be split 118 | /// e.g 40% means that 40% of the RA that user deposit will be splitted into CT and DS 119 | /// the CT will be held in the vault while the DS is held in the vault reserve to be selled in the router 120 | uint256 ctHeldPercetage; 121 | /// @notice dsId => totalRA. will be updated on every new issuance, so dsId 1 would be update at new issuance of dsId 2 122 | mapping(uint256 => uint256) totalRaSnapshot; 123 | } 124 | 125 | /** 126 | * @dev VaultConfig structure for VaultConfig Contract 127 | */ 128 | struct VaultConfig { 129 | bool isDepositPaused; 130 | bool isWithdrawalPaused; 131 | NavCircuitBreaker navCircuitBreaker; 132 | } 133 | 134 | struct NavCircuitBreaker { 135 | uint256 snapshot0; 136 | uint256 lastUpdate0; 137 | uint256 snapshot1; 138 | uint256 lastUpdate1; 139 | uint256 navThreshold; 140 | } 141 | -------------------------------------------------------------------------------- /contracts/libraries/TransferHelper.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.24; 3 | 4 | import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; 5 | import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 6 | import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 7 | import {ERC20Burnable} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; 8 | 9 | library TransferHelper { 10 | uint8 internal constant TARGET_DECIMALS = 18; 11 | 12 | function normalizeDecimals(uint256 amount, uint8 decimalsBefore, uint8 decimalsAfter) 13 | internal 14 | pure 15 | returns (uint256) 16 | { 17 | // If we need to increase the decimals 18 | if (decimalsBefore > decimalsAfter) { 19 | // Then we shift right the amount by the number of decimals 20 | amount = amount / 10 ** (decimalsBefore - decimalsAfter); 21 | // If we need to decrease the number 22 | } else if (decimalsBefore < decimalsAfter) { 23 | // then we shift left by the difference 24 | amount = amount * 10 ** (decimalsAfter - decimalsBefore); 25 | } 26 | // If nothing changed this is a no-op 27 | return amount; 28 | } 29 | 30 | function tokenNativeDecimalsToFixed(uint256 amount, IERC20Metadata token) internal view returns (uint256) { 31 | uint8 decimals = token.decimals(); 32 | return normalizeDecimals(amount, decimals, TARGET_DECIMALS); 33 | } 34 | 35 | function tokenNativeDecimalsToFixed(uint256 amount, address token) internal view returns (uint256) { 36 | return tokenNativeDecimalsToFixed(amount, IERC20Metadata(token)); 37 | } 38 | 39 | function fixedToTokenNativeDecimals(uint256 amount, IERC20Metadata token) internal view returns (uint256) { 40 | uint8 decimals = token.decimals(); 41 | return normalizeDecimals(amount, TARGET_DECIMALS, decimals); 42 | } 43 | 44 | function fixedToTokenNativeDecimals(uint256 amount, address token) internal view returns (uint256) { 45 | return fixedToTokenNativeDecimals(amount, IERC20Metadata(token)); 46 | } 47 | 48 | function transferNormalize(ERC20 token, address _to, uint256 _amount) internal returns (uint256 amount) { 49 | amount = fixedToTokenNativeDecimals(_amount, token); 50 | SafeERC20.safeTransfer(token, _to, amount); 51 | } 52 | 53 | function transferNormalize(address token, address _to, uint256 _amount) internal returns (uint256 amount) { 54 | return transferNormalize(ERC20(token), _to, _amount); 55 | } 56 | 57 | function transferFromNormalize(ERC20 token, address _from, uint256 _amount) internal returns (uint256 amount) { 58 | amount = fixedToTokenNativeDecimals(_amount, token); 59 | SafeERC20.safeTransferFrom(token, _from, address(this), amount); 60 | } 61 | 62 | function transferFromNormalize(address token, address _from, uint256 _amount) internal returns (uint256 amount) { 63 | return transferFromNormalize(ERC20(token), _from, _amount); 64 | } 65 | 66 | function burnNormalize(ERC20Burnable token, uint256 _amount) internal returns (uint256 amount) { 67 | amount = fixedToTokenNativeDecimals(_amount, token); 68 | token.burn(amount); 69 | } 70 | 71 | function burnNormalize(address token, uint256 _amount) internal returns (uint256 amount) { 72 | return burnNormalize(ERC20Burnable(token), _amount); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /contracts/libraries/VaultBalancesLib.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.24; 3 | 4 | import {ICorkHook} from "./../interfaces/UniV4/IMinimalHook.sol"; 5 | import {State} from "./State.sol"; 6 | import {DepegSwap} from "./DepegSwapLib.sol"; 7 | import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol"; 8 | 9 | library VaultBalanceLibrary { 10 | function subtractLpBalance(State storage self, uint256 amount) internal { 11 | self.vault.balances.lpBalance -= amount; 12 | } 13 | 14 | function addLpBalance(State storage self, uint256 amount) internal { 15 | self.vault.balances.lpBalance += amount; 16 | } 17 | 18 | function lpBalance(State storage self) internal view returns (uint256) { 19 | return self.vault.balances.lpBalance; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /contracts/libraries/VaultPoolLib.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.24; 3 | 4 | import {VaultPool} from "./State.sol"; 5 | import {MathHelper} from "./MathHelper.sol"; 6 | 7 | /** 8 | * @title VaultPool Library Contract 9 | * @author Cork Team 10 | * @notice VaultPool Library implements features related to LV Pools(liquidity Vault Pools) 11 | */ 12 | library VaultPoolLibrary { 13 | function reserve(VaultPool storage self, uint256 totalLvIssued, uint256 addedRa, uint256 addedPa) internal { 14 | // new protocol amendement, no need to reserve for lv 15 | uint256 totalLvWithdrawn = 0; 16 | 17 | // RA 18 | uint256 totalRa = self.withdrawalPool.raBalance + addedRa; 19 | (, uint256 attributedToAmm, uint256 ratePerLv) = 20 | MathHelper.separateLiquidity(totalRa, totalLvIssued, totalLvWithdrawn); 21 | 22 | self.ammLiquidityPool.balance = attributedToAmm; 23 | self.withdrawalPool.raExchangeRate = ratePerLv; 24 | 25 | // PA 26 | uint256 totalPa = self.withdrawalPool.paBalance + addedPa; 27 | (, attributedToAmm, ratePerLv) = MathHelper.separateLiquidity(totalPa, totalLvIssued, 0); 28 | 29 | self.withdrawalPool.paBalance = attributedToAmm; 30 | self.withdrawalPool.paExchangeRate = ratePerLv; 31 | 32 | assert(totalRa == self.withdrawalPool.raBalance + self.ammLiquidityPool.balance); 33 | } 34 | 35 | function rationedToAmm(VaultPool storage self, uint256 ratio) 36 | internal 37 | view 38 | returns (uint256 ra, uint256 ct, uint256 originalBalance) 39 | { 40 | originalBalance = self.ammLiquidityPool.balance; 41 | 42 | (ra, ct) = MathHelper.calculateProvideLiquidityAmountBasedOnCtPrice(originalBalance, ratio); 43 | } 44 | 45 | function resetAmmPool(VaultPool storage self) internal { 46 | self.ammLiquidityPool.balance = 0; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /contracts/libraries/uni-v2/UniswapV2Library.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.24; 3 | 4 | library MinimalUniswapV2Library { 5 | error InvalidToken(); 6 | error INSUFFICIENT_INPUT_AMOUNT(); 7 | error INSUFFICIENT_OUTPUT_AMOUNT(); 8 | error INSUFFICIENT_LIQUIDITY(); 9 | 10 | // 0.3% 11 | uint256 public constant FEE = 997; 12 | // 0% 13 | uint256 public constant NO_FEE = 1000; 14 | 15 | // returns sorted token addresses, used to handle return values from pairs sorted in this order 16 | function sortTokensUnsafeWithAmount(address ra, address ct, uint256 raAmount, uint256 ctAmount) 17 | internal 18 | pure 19 | returns (address token0, address token1, uint256 amount0, uint256 amount1) 20 | { 21 | assert(ra != ct); 22 | (token0, amount0, token1, amount1) = ra < ct ? (ra, raAmount, ct, ctAmount) : (ct, ctAmount, ra, raAmount); 23 | assert(token0 != address(0)); 24 | } 25 | 26 | function reverseSortWithAmount112( 27 | address token0, 28 | address token1, 29 | address ra, 30 | address ct, 31 | uint112 token0Amount, 32 | uint112 token1Amount 33 | ) internal pure returns (uint112 raAmountOut, uint112 ctAmountOut) { 34 | if (token0 == ra && token1 == ct) { 35 | raAmountOut = token0Amount; 36 | ctAmountOut = token1Amount; 37 | } else if (token0 == ct && token1 == ra) { 38 | raAmountOut = token1Amount; 39 | ctAmountOut = token0Amount; 40 | } else { 41 | revert InvalidToken(); 42 | } 43 | } 44 | 45 | function reverseSortWithAmount224( 46 | address token0, 47 | address token1, 48 | address ra, 49 | address ct, 50 | uint256 token0Amount, 51 | uint256 token1Amount 52 | ) internal pure returns (uint256 raAmountOut, uint256 ctAmountOut) { 53 | if (token0 == ra && token1 == ct) { 54 | raAmountOut = token0Amount; 55 | ctAmountOut = token1Amount; 56 | } else if (token0 == ct && token1 == ra) { 57 | raAmountOut = token1Amount; 58 | ctAmountOut = token0Amount; 59 | } else { 60 | revert InvalidToken(); 61 | } 62 | } 63 | 64 | // given an input amount of an asset and pair reserves, returns the maximum output amount of the other asset 65 | // WARNING: won't apply fee since we don't take fee from user right now 66 | function getAmountOut(uint256 amountIn, uint256 reserveIn, uint256 reserveOut) 67 | internal 68 | pure 69 | returns (uint256 amountOut) 70 | { 71 | if (amountIn == 0) { 72 | revert INSUFFICIENT_INPUT_AMOUNT(); 73 | } 74 | if (reserveIn == 0 || reserveOut == 0) { 75 | revert INSUFFICIENT_LIQUIDITY(); 76 | } 77 | uint256 amountInWithFee = amountIn * NO_FEE; 78 | uint256 numerator = amountInWithFee * reserveOut; 79 | uint256 denominator = reserveIn * 1000; 80 | amountOut = numerator / denominator; 81 | } 82 | 83 | // given an output amount of an asset and pair reserves, returns a required input amount of the other asset 84 | // WARNING: won't apply fee since we don't take fee from user right now 85 | function getAmountIn(uint256 amountOut, uint256 reserveIn, uint256 reserveOut) 86 | internal 87 | pure 88 | returns (uint256 amountIn) 89 | { 90 | if (amountOut == 0) { 91 | revert INSUFFICIENT_OUTPUT_AMOUNT(); 92 | } 93 | if (reserveIn == 0 || reserveOut == 0) { 94 | revert INSUFFICIENT_LIQUIDITY(); 95 | } 96 | uint256 numerator = reserveIn * amountOut * 1000; 97 | uint256 denominator = reserveOut * NO_FEE; 98 | amountIn = (numerator / denominator); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /contracts/offchain-helpers/LpHelper.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.24; 3 | 4 | import {ICorkHook} from "../interfaces/UniV4/IMinimalHook.sol"; 5 | import {Id, Pair} from "./../libraries/Pair.sol"; 6 | import {LiquidityToken} from "Cork-Hook/LiquidityToken.sol"; 7 | import {LpParser} from "./../libraries/LpSymbolParser.sol"; 8 | import {IErrors} from "./../interfaces/IErrors.sol"; 9 | import {ModuleCore} from "./../core/ModuleCore.sol"; 10 | import {ILpHelper}from "./../interfaces/offchain-helpers/ILpHelper.sol"; 11 | 12 | contract LpHelper is ILpHelper { 13 | ICorkHook public hook; 14 | ModuleCore public moduleCore; 15 | 16 | constructor(address _hook, address _moduleCore) { 17 | hook = ICorkHook(_hook); 18 | moduleCore = ModuleCore(_moduleCore); 19 | } 20 | 21 | function getReserve(address lp) external view returns (uint256 raReserve, uint256 ctReserve) { 22 | LiquidityToken token = LiquidityToken(lp); 23 | 24 | if (token.owner() != address(hook)) revert IErrors.InvalidToken(); 25 | 26 | (address ra, address ct) = LpParser.parse(token.symbol()); 27 | 28 | (raReserve, ctReserve) = hook.getReserves(ra, ct); 29 | } 30 | 31 | function getReserve(Id id) external view returns (uint256 raReserve, uint256 ctReserve) { 32 | uint256 epoch = moduleCore.lastDsId(id); 33 | _getReserve(id, epoch); 34 | } 35 | 36 | function getReserve(Id id, uint256 dsId) external view returns (uint256 raReserve, uint256 ctReserve) { 37 | _getReserve(id, dsId); 38 | } 39 | 40 | function _getReserve(Id id, uint256 epoch) internal view returns (uint256 raReserve, uint256 ctReserve) { 41 | (address ra, address ct) = _getRaCt(id, epoch); 42 | (raReserve, ctReserve) = hook.getReserves(ra, ct); 43 | } 44 | 45 | function _getRaCt(Id id, uint256 epoch) internal view returns (address ra, address ct) { 46 | (, address ra,,,) = moduleCore.markets(id); 47 | 48 | (address ct,) = moduleCore.swapAsset(id, epoch); 49 | } 50 | 51 | function getLpToken(Id id) external view returns (address liquidityToken) { 52 | uint256 epoch = moduleCore.lastDsId(id); 53 | (address ra, address ct) = _getRaCt(id, epoch); 54 | 55 | liquidityToken = hook.getLiquidityToken(ra, ct); 56 | } 57 | 58 | function getLpToken(Id id, uint256 dsId) external view returns (address liquidityToken) { 59 | (address ra, address ct) = _getRaCt(id, dsId); 60 | 61 | liquidityToken = hook.getLiquidityToken(ra, ct); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /contracts/readers/PriceReader.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.24; 3 | 4 | import {IUniswapV2Factory} from "../interfaces/uniswap-v2/factory.sol"; 5 | import {IUniswapV2Router02} from "../interfaces/uniswap-v2/RouterV2.sol"; 6 | /** 7 | * @title PriceFeedReader Contract 8 | * @author Cork Team 9 | * @notice PriceFeedReader contract for reading price details of assets 10 | */ 11 | 12 | contract UniswapPriceReader { 13 | address public factory; 14 | address public router; 15 | 16 | error PairNotExist(); 17 | 18 | constructor(address _factory, address _router) { 19 | factory = _factory; 20 | router = _router; 21 | } 22 | 23 | // Get the price of Destination Token in terms of source token 24 | function getTokenPrice(address destToken, address sourceToken) public view returns (uint256 price) { 25 | address pair = IUniswapV2Factory(factory).getPair(destToken, sourceToken); 26 | if (pair == address(0)) { 27 | revert PairNotExist(); 28 | } 29 | 30 | address[] memory path = new address[](2); 31 | path[0] = destToken; 32 | path[1] = sourceToken; 33 | price = IUniswapV2Router02(router).getAmountsOut(1e18, path)[1]; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /contracts/tokens/CETH.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.24; 3 | 4 | import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 5 | import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; 6 | 7 | /** 8 | * @title CETH Contract 9 | * @author Cork Team 10 | * @notice CETH contract represents Cork ETH with role-based minting 11 | */ 12 | contract CETH is ERC20, AccessControl { 13 | // Define a new role identifier for minters 14 | bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); 15 | bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE"); 16 | 17 | constructor(string memory name, string memory symbol) ERC20(name, symbol) { 18 | // Grant the contract deployer the default admin role: they can grant and revoke roles 19 | _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); 20 | 21 | // Grant the contract deployer the minter role so they can mint initially 22 | _grantRole(MINTER_ROLE, msg.sender); 23 | _grantRole(BURNER_ROLE, msg.sender); 24 | } 25 | 26 | /** 27 | * @dev Function for minting new Cork ETH (Only accounts with MINTER_ROLE can mint) 28 | * @param to Address of destination wallet 29 | * @param amount number of CETH to be minted 30 | */ 31 | function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) { 32 | _mint(to, amount); 33 | } 34 | 35 | /** 36 | * @dev Function for burning new Cork ETH (Only accounts with Burner_ROLE can burn) 37 | * @param from Address of from wallet 38 | * @param amount number of CETH to be burned 39 | */ 40 | function burn(address from, uint256 amount) public onlyRole(BURNER_ROLE) { 41 | _burn(from, amount); 42 | } 43 | 44 | /** 45 | * @dev Grant MINTER_ROLE to a new account (Only admin can grant) 46 | * @param account Address of the new minter 47 | */ 48 | function addMinter(address account) public onlyRole(DEFAULT_ADMIN_ROLE) { 49 | grantRole(MINTER_ROLE, account); 50 | } 51 | 52 | /** 53 | * @dev Revoke MINTER_ROLE from an account (Only admin can revoke) 54 | * @param account Address of the minter to revoke 55 | */ 56 | function removeMinter(address account) public onlyRole(DEFAULT_ADMIN_ROLE) { 57 | revokeRole(MINTER_ROLE, account); 58 | } 59 | 60 | /** 61 | * @dev Grant BURNER_ROLE to a new account (Only admin can grant) 62 | * @param account Address of the new burner 63 | */ 64 | function addBurner(address account) public onlyRole(DEFAULT_ADMIN_ROLE) { 65 | grantRole(BURNER_ROLE, account); 66 | } 67 | 68 | /** 69 | * @dev Revoke BURNER_ROLE from an account (Only admin can revoke) 70 | * @param account Address of the burner to revoke 71 | */ 72 | function removeBurner(address account) public onlyRole(DEFAULT_ADMIN_ROLE) { 73 | revokeRole(BURNER_ROLE, account); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /contracts/tokens/CUSD.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.24; 3 | 4 | import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 5 | import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; 6 | 7 | /** 8 | * @title CUSD Contract 9 | * @author Cork Team 10 | * @notice CUSD contract represents Cork USD with role-based minting 11 | */ 12 | contract CUSD is ERC20, AccessControl { 13 | // Define a new role identifier for minters 14 | bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); 15 | bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE"); 16 | 17 | constructor(string memory name, string memory symbol) ERC20(name, symbol) { 18 | // Grant the contract deployer the default admin role: they can grant and revoke roles 19 | _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); 20 | 21 | // Grant the contract deployer the minter role so they can mint initially 22 | _grantRole(MINTER_ROLE, msg.sender); 23 | _grantRole(BURNER_ROLE, msg.sender); 24 | } 25 | 26 | /** 27 | * @dev Function for minting new Cork USD (Only accounts with MINTER_ROLE can mint) 28 | * @param to Address of destination wallet 29 | * @param amount number of CUSD to be minted 30 | */ 31 | function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) { 32 | _mint(to, amount); 33 | } 34 | 35 | /** 36 | * @dev Function for burning new Cork USD (Only accounts with Burner_ROLE can burn) 37 | * @param from Address of from wallet 38 | * @param amount number of CUSD to be burned 39 | */ 40 | function burn(address from, uint256 amount) public onlyRole(BURNER_ROLE) { 41 | _burn(from, amount); 42 | } 43 | 44 | /** 45 | * @dev Grant MINTER_ROLE to a new account (Only admin can grant) 46 | * @param account Address of the new minter 47 | */ 48 | function addMinter(address account) public onlyRole(DEFAULT_ADMIN_ROLE) { 49 | grantRole(MINTER_ROLE, account); 50 | } 51 | 52 | /** 53 | * @dev Revoke MINTER_ROLE from an account (Only admin can revoke) 54 | * @param account Address of the minter to revoke 55 | */ 56 | function removeMinter(address account) public onlyRole(DEFAULT_ADMIN_ROLE) { 57 | revokeRole(MINTER_ROLE, account); 58 | } 59 | 60 | /** 61 | * @dev Grant BURNER_ROLE to a new account (Only admin can grant) 62 | * @param account Address of the new burner 63 | */ 64 | function addBurner(address account) public onlyRole(DEFAULT_ADMIN_ROLE) { 65 | grantRole(BURNER_ROLE, account); 66 | } 67 | 68 | /** 69 | * @dev Revoke BURNER_ROLE from an account (Only admin can revoke) 70 | * @param account Address of the burner to revoke 71 | */ 72 | function removeBurner(address account) public onlyRole(DEFAULT_ADMIN_ROLE) { 73 | revokeRole(BURNER_ROLE, account); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /docs/pull_request_template.md: -------------------------------------------------------------------------------- 1 | # Changes 2 | 3 | List of Changes: 4 | - changes 5 | 6 | 7 | # Notes 8 | Write here there's a note for this specific feature(e.g usage guide) 9 | 10 | # Warning 11 | Write here if there's a warning regarding this feature(e.g this feature is unsafe on certain conditions) 12 | 13 | -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "contracts" 3 | evm_version = "cancun" 4 | out = "out" 5 | libs = ["node_modules", "lib"] 6 | test = "test" 7 | cache_path = "cache_forge" 8 | ffi = true 9 | ast = true 10 | build_info = true 11 | extra_output = ["storageLayout"] 12 | optimizer = true 13 | optimizer_runs = 200 14 | solc_version = "0.8.26" 15 | fs_permissions = [{ access = "read", path = "./"}] 16 | 17 | 18 | # Compile only the production code with IR see https://github.com/foundry-rs/forge-std/issues/321#issuecomment-1462351522 19 | [profile.optimized] 20 | out = "optimized-out" 21 | test = "test" 22 | via_ir = true 23 | optimizer = true 24 | optimizer_runs = 200 -------------------------------------------------------------------------------- /hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import { HardhatUserConfig } from "hardhat/config"; 2 | import "@nomicfoundation/hardhat-ethers"; 3 | import "hardhat-deploy"; 4 | import "hardhat-deploy-ethers"; 5 | import "@nomicfoundation/hardhat-toolbox-viem"; 6 | import "hardhat-gas-reporter"; 7 | import "@nomicfoundation/hardhat-viem"; 8 | import loadEnv from "dotenv"; 9 | import "hardhat-contract-sizer"; 10 | import chai from "chai"; 11 | import { solidity } from "ethereum-waffle"; 12 | import "solidity-coverage"; 13 | import "@nomicfoundation/hardhat-foundry"; 14 | 15 | chai.use(solidity); 16 | // import "@nomicfoundation/hardhat-chai-matchers"; 17 | 18 | loadEnv.config(); 19 | 20 | const config: HardhatUserConfig = { 21 | solidity: { 22 | version: "0.8.24", 23 | settings: { 24 | optimizer: { 25 | enabled: true, 26 | runs: 200, 27 | }, 28 | evmVersion: "cancun", 29 | }, 30 | }, 31 | networks: { 32 | hardhat: {}, 33 | sepolia: { 34 | url: "https://eth-sepolia.api.onfinality.io/public", 35 | chainId: 11155111, 36 | accounts: [process.env.PRIVATE_KEY!], 37 | enableTransientStorage: true, 38 | loggingEnabled: true, 39 | }, 40 | }, 41 | ignition: { 42 | requiredConfirmations: 0, 43 | }, 44 | gasReporter: { 45 | enabled: process.env.REPORT_GAS === "true" ? true : false, 46 | currency: "USD", 47 | coinmarketcap: process.env.CMC_API_KEY, 48 | outputJSON: true, 49 | outputJSONFile: "gas-report.json", 50 | includeIntrinsicGas: true, 51 | }, 52 | contractSizer: { 53 | runOnCompile: true, 54 | only: [ 55 | "ModuleCore", 56 | "AssetFactory", 57 | "MathHelper", 58 | "VaultLibrary", 59 | "PsmLibrary", 60 | "RouterState", 61 | ], 62 | }, 63 | 64 | }; 65 | 66 | export default config; 67 | -------------------------------------------------------------------------------- /ignition/modules/core.ts: -------------------------------------------------------------------------------- 1 | import { buildModule } from "@nomicfoundation/hardhat-ignition/modules"; 2 | import { flashSwapMath, mathHelper, vaultLib } from "./lib"; 3 | import { uniV2Factory, uniV2router } from "./uniV2"; 4 | import moduleCore from "../../artifacts/contracts/core/ModuleCore.sol/ModuleCore.json"; 5 | 6 | export const assetFactory = buildModule("AssetFactory", (m) => { 7 | const contract = m.contract("AssetFactory"); 8 | 9 | return { assetFactory: contract }; 10 | }); 11 | 12 | export const corkConfig = buildModule("CorkConfig", (m) => { 13 | const contract = m.contract("CorkConfig"); 14 | return { CorkConfig: contract }; 15 | }); 16 | 17 | export const flashSwapRouter = buildModule("FlashSwapRouter", (m) => { 18 | const { MathHelper } = m.useModule(mathHelper); 19 | const { SwapperMathLibrary } = m.useModule(flashSwapMath); 20 | 21 | const contract = m.contract("RouterState", [], { 22 | libraries: { 23 | MathHelper, 24 | SwapperMathLibrary, 25 | }, 26 | }); 27 | 28 | return { FlashSwapRouter: contract }; 29 | }); 30 | 31 | export const ModuleCore = buildModule("ModuleCore", (m) => { 32 | const { MathHelper } = m.useModule(mathHelper); 33 | const { VaultLibrary } = m.useModule(vaultLib); 34 | 35 | const _assetFactory = m.getParameter("assetFactory"); 36 | const _uniV2Factory = m.getParameter("uniV2Factory"); 37 | const _flashSwapRouter = m.getParameter("flashSwapRouter"); 38 | const _uniV2Router = m.getParameter("uniV2Router"); 39 | const _corkConfig = m.getParameter("corkConfig"); 40 | const _baseFee = m.getParameter("psmBaseFeeRedemption"); 41 | 42 | const contract = m.contract( 43 | "ModuleCore", 44 | moduleCore, 45 | [ 46 | _assetFactory, 47 | _uniV2Factory, 48 | _flashSwapRouter, 49 | _uniV2Router, 50 | _corkConfig, 51 | _baseFee, 52 | ], 53 | { 54 | libraries: { 55 | MathHelper: MathHelper, 56 | VaultLibrary: VaultLibrary, 57 | }, 58 | } 59 | ); 60 | 61 | return { 62 | ModuleCore: contract, 63 | }; 64 | }); 65 | -------------------------------------------------------------------------------- /ignition/modules/lib.ts: -------------------------------------------------------------------------------- 1 | import { buildModule } from "@nomicfoundation/hardhat-ignition/modules"; 2 | 3 | export const vaultLib = buildModule("VaultLib", (m) => { 4 | const mathHelper = m.library("MathHelper"); 5 | 6 | const contract = m.library("VaultLibrary", { 7 | libraries: { 8 | MathHelper: mathHelper, 9 | }, 10 | }); 11 | 12 | return { VaultLibrary: contract }; 13 | }); 14 | 15 | export const psmLib = buildModule("PsmLib", (m) => { 16 | const contract = m.library("PsmLibrary"); 17 | 18 | return { PsmLibrary: contract }; 19 | }); 20 | 21 | export const mathHelper = buildModule("MathHelper", (m) => { 22 | const contract = m.library("MathHelper"); 23 | 24 | return { MathHelper: contract }; 25 | }); 26 | 27 | export const flashSwapMath = buildModule("FlashSwapMath", (m) => { 28 | const contract = m.library("SwapperMathLibrary"); 29 | 30 | return { SwapperMathLibrary: contract }; 31 | }); 32 | -------------------------------------------------------------------------------- /ignition/modules/uniV2.ts: -------------------------------------------------------------------------------- 1 | import { buildModule } from "@nomicfoundation/hardhat-ignition/modules"; 2 | import UNIV2FACTORY from "../../test/helper/ext-abi/hardhat/uni-v2-factory.json"; 3 | import UNIV2ROUTER from "../../test/helper/ext-abi/hardhat/uni-v2-router.json"; 4 | import { artifacts } from "hardhat"; 5 | import { Artifact } from "hardhat/types"; 6 | import { flashSwapRouter } from "./core"; 7 | 8 | export const uniV2router = buildModule("UniV2Router", (m) => { 9 | const flashSwap = m.getParameter("flashSwapRouter"); 10 | const factory = m.getParameter("uniV2Factory"); 11 | 12 | // we insert this dynamically since, it can be a live contract 13 | const weth = m.getParameter("weth"); 14 | 15 | const routerArtifact: Artifact = { 16 | _format: "hh-sol-artifact-1", 17 | abi: UNIV2ROUTER.abi, 18 | bytecode: UNIV2ROUTER.evm.bytecode.object, 19 | linkReferences: UNIV2ROUTER.evm.bytecode.linkReferences, 20 | contractName: "UniV2Router", 21 | deployedBytecode: UNIV2ROUTER.evm.deployedBytecode.object, 22 | deployedLinkReferences: UNIV2ROUTER.evm.deployedBytecode.linkReferences, 23 | sourceName: "UniV2Router.sol", 24 | }; 25 | 26 | const contract = m.contract( 27 | "UniV2Router", 28 | routerArtifact, 29 | [factory, weth, flashSwap] 30 | ); 31 | 32 | return { UniV2Router: contract }; 33 | }); 34 | 35 | export const uniV2Factory = buildModule("uniV2Factory", (m) => { 36 | // const flashSwap = m.getParameter("flashSwapRouter"); 37 | // const feeToSetter = m.getParameter("feeToSetter"); 38 | 39 | const routerArtifact: Artifact = { 40 | _format: "hh-sol-artifact-1", 41 | abi: UNIV2FACTORY.abi, 42 | bytecode: UNIV2FACTORY.evm.bytecode.object, 43 | linkReferences: UNIV2FACTORY.evm.bytecode.linkReferences, 44 | contractName: "UniV2Factory", 45 | deployedBytecode: UNIV2FACTORY.evm.deployedBytecode.object, 46 | deployedLinkReferences: UNIV2FACTORY.evm.deployedBytecode.linkReferences, 47 | sourceName: "UniV2Factory.sol", 48 | }; 49 | 50 | const contract = m.contract( 51 | "UniV2Factory", 52 | routerArtifact, 53 | [ 54 | "0x3E995c17172eA3E23505Adfe5630df395A738e51", 55 | "0x3E995c17172eA3E23505Adfe5630df395A738e51", 56 | ] 57 | ); 58 | 59 | return { UniV2Factory: contract }; 60 | }); 61 | 62 | export const dummyWETH = buildModule("DummyWETH", (m) => { 63 | const contract = m.contract("DummyWETH"); 64 | return { DummyWETH: contract }; 65 | }); 66 | 67 | export default uniV2Factory; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Depeg-Swap-V1", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "author": "ziankork ", 6 | "license": "MIT", 7 | "scripts": { 8 | "prepare": "husky || true", 9 | "test": "npx hardhat test", 10 | "test:all": "npx hardhat clean && forge clean && forge test -vvv && npx hardhat test", 11 | "build": "npx hardhat compile", 12 | "compile:organise": "npx hardhat clean && npx hardhat compile && node script/node-scripts/organiseArtifacts.js", 13 | "lint:contract": "npx solhint 'contracts/**/*.sol'", 14 | "lint:contract:fix": "npx solhint 'contracts/**/*.sol' --fix" 15 | }, 16 | "devDependencies": { 17 | "@nomicfoundation/hardhat-chai-matchers": "^2.0.7", 18 | "@nomicfoundation/hardhat-ignition": "^0.15.0", 19 | "@nomicfoundation/hardhat-ignition-viem": "^0.15.0", 20 | "@nomicfoundation/hardhat-network-helpers": "^1.0.0", 21 | "@nomicfoundation/hardhat-toolbox-viem": "^3.0.0", 22 | "@nomicfoundation/hardhat-verify": "^2.0.0", 23 | "@nomicfoundation/hardhat-viem": "^2.0.2", 24 | "@types/chai": "^4.2.0", 25 | "@types/chai-as-promised": "^7.1.6", 26 | "@types/mocha": ">=9.1.0", 27 | "@types/node": ">=18.0.0", 28 | "chai": "^4.2.0", 29 | "ethereum-waffle": "^4.0.10", 30 | "hardhat": "^2.22.3", 31 | "hardhat-deploy": "^0.12.4", 32 | "hardhat-deploy-ethers": "^0.4.2", 33 | "hardhat-gas-reporter": "^2.1.1", 34 | "husky": "^9.1.7", 35 | "solidity-coverage": "^0.8.0", 36 | "ts-node": ">=8.0.0", 37 | "typescript": "~5.0.4", 38 | "viem": "^2.13.7" 39 | }, 40 | "dependencies": { 41 | "@balancer-labs/v2-solidity-utils": "^4.0.0", 42 | "@nomicfoundation/hardhat-ethers": "^3.0.6", 43 | "@nomicfoundation/hardhat-foundry": "^1.1.2", 44 | "@openzeppelin/contracts": "^5.0.2", 45 | "@openzeppelin/contracts-upgradeable": "^5.0.2", 46 | "@openzeppelin/hardhat-upgrades": "^3.1.0", 47 | "@uniswap/lib": "4.0.1-alpha", 48 | "@uniswap/v2-core": "1.0.0", 49 | "dotenv": "^16.4.5", 50 | "ethers": "5.6.2", 51 | "hardhat-contract-sizer": "^2.10.0", 52 | "solhint": "^5.0.5" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /remappings.txt: -------------------------------------------------------------------------------- 1 | @ensdomains/=lib/Cork-Hook/lib/v4-periphery/lib/v4-core/node_modules/@ensdomains/ 2 | @ethereum-waffle/=node_modules/@ethereum-waffle/ 3 | @openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/ 4 | @openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/ 5 | @uniswap/v4-core/=lib/Cork-Hook/lib/v4-periphery/lib/v4-core/ 6 | Cork-Hook/=lib/Cork-Hook/src/ 7 | Depeg-swap/=lib/Cork-Hook/lib/Depeg-swap/ 8 | ds-test/=lib/openzeppelin-contracts-upgradeable/lib/forge-std/lib/ds-test/src/ 9 | erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/ 10 | forge-gas-snapshot/=lib/Cork-Hook/lib/v4-periphery/lib/v4-core/lib/forge-gas-snapshot/src/ 11 | forge-std/=lib/forge-std/src/ 12 | hardhat-deploy/=node_modules/hardhat-deploy/ 13 | hardhat/=node_modules/hardhat/ 14 | openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/ 15 | openzeppelin-contracts/=lib/openzeppelin-contracts/ 16 | permit2/=lib/permit2/ 17 | solmate/=lib/Cork-Hook/lib/v4-periphery/lib/v4-core/lib/solmate/ 18 | v2-core/=lib/v2-core/contracts/ 19 | v2-periphery/=lib/v2-periphery/contracts/ 20 | v4-core/=lib/Cork-Hook/lib/v4-periphery/lib/v4-core/src/ 21 | v4-periphery/=lib/Cork-Hook/lib/v4-periphery/ 22 | @prb/math/=lib/prb-math/ 23 | BokkyPooBahsDateTimeLibrary/=lib/BokkyPooBahsDateTimeLibrary/contracts/ -------------------------------------------------------------------------------- /script/foundry-scripts/Utils/HookMiner.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.21; 3 | 4 | /// @title HookMiner - a library for mining hook addresses 5 | /// @dev This library is intended for `forge test` environments. There may be gotchas when using salts in `forge script` or `forge create` 6 | library HookMiner { 7 | // mask to slice out the bottom 14 bit of the address 8 | uint160 constant FLAG_MASK = 0x3FFF; 9 | 10 | // Maximum number of iterations to find a salt, avoid infinite loops 11 | uint256 constant MAX_LOOP = 100_000; 12 | 13 | /// @notice Find a salt that produces a hook address with the desired `flags` 14 | /// @param deployer The address that will deploy the hook. In `forge test`, this will be the test contract `address(this)` or the pranking address 15 | /// In `forge script`, this should be `0x4e59b44847b379578588920cA78FbF26c0B4956C` (CREATE2 Deployer Proxy) 16 | /// @param flags The desired flags for the hook address 17 | /// @param creationCode The creation code of a hook contract. Example: `type(Counter).creationCode` 18 | /// @param constructorArgs The encoded constructor arguments of a hook contract. Example: `abi.encode(address(manager))` 19 | /// @return hookAddress salt and corresponding address that was found. The salt can be used in `new Hook{salt: salt}()` 20 | function find(address deployer, uint160 flags, bytes memory creationCode, bytes memory constructorArgs) 21 | internal 22 | view 23 | returns (address, bytes32) 24 | { 25 | address hookAddress; 26 | bytes memory creationCodeWithArgs = abi.encodePacked(creationCode, constructorArgs); 27 | 28 | uint256 salt; 29 | for (salt; salt < MAX_LOOP; salt++) { 30 | hookAddress = computeAddress(deployer, salt, creationCodeWithArgs); 31 | if (uint160(hookAddress) & FLAG_MASK == flags && hookAddress.code.length == 0) { 32 | return (hookAddress, bytes32(salt)); 33 | } 34 | } 35 | revert("HookMiner: could not find salt"); 36 | } 37 | 38 | /// @notice Precompute a contract address deployed via CREATE2 39 | /// @param deployer The address that will deploy the hook. In `forge test`, this will be the test contract `address(this)` or the pranking address 40 | /// In `forge script`, this should be `0x4e59b44847b379578588920cA78FbF26c0B4956C` (CREATE2 Deployer Proxy) 41 | /// @param salt The salt used to deploy the hook 42 | /// @param creationCode The creation code of a hook contract 43 | function computeAddress(address deployer, uint256 salt, bytes memory creationCode) 44 | internal 45 | pure 46 | returns (address hookAddress) 47 | { 48 | return address( 49 | uint160(uint256(keccak256(abi.encodePacked(bytes1(0xFF), deployer, salt, keccak256(creationCode))))) 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /script/foundry-scripts/Utils/Utils.s.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.24; 2 | 3 | library Utils { 4 | // Function to convert Wei to Ether format (string) 5 | function formatEther(uint256 weiValue) public pure returns (string memory) { 6 | // Convert WEI to Ether by dividing by 1e18 (Ether has 18 decimal places) 7 | uint256 integerPart = weiValue / 1e18; 8 | uint256 decimalPart = weiValue % 1e18; 9 | 10 | // Convert integer and decimal parts to strings 11 | string memory integerString = uint2str(integerPart); 12 | string memory decimalString = uint2str(decimalPart); 13 | 14 | // Ensure decimal part is 18 digits by padding with leading zeros 15 | decimalString = padDecimal(decimalString); 16 | 17 | // Concatenate integer and decimal parts 18 | return string(abi.encodePacked(integerString, ".", decimalString)); 19 | } 20 | 21 | // Convert uint256 to string 22 | function uint2str(uint256 _i) internal pure returns (string memory) { 23 | if (_i == 0) { 24 | return "0"; 25 | } 26 | uint256 j = _i; 27 | uint256 len; 28 | while (j != 0) { 29 | len++; 30 | j /= 10; 31 | } 32 | bytes memory bstr = new bytes(len); 33 | uint256 k = len; 34 | while (_i != 0) { 35 | k = k - 1; 36 | bstr[k] = bytes1(uint8(48 + _i % 10)); 37 | _i /= 10; 38 | } 39 | return string(bstr); 40 | } 41 | 42 | // Pad the decimal part to ensure it has 18 digits 43 | function padDecimal(string memory decimalString) internal pure returns (string memory) { 44 | uint256 decimalLength = bytes(decimalString).length; 45 | if (decimalLength < 18) { 46 | for (uint256 i = 0; i < 18 - decimalLength; i++) { 47 | decimalString = string(abi.encodePacked("0", decimalString)); 48 | } 49 | } 50 | return decimalString; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /script/hardhat-scripts/deploy.ts: -------------------------------------------------------------------------------- 1 | import hre from "hardhat"; 2 | import dotenv from "dotenv"; 3 | 4 | import * as core from "../../ignition/modules/core"; 5 | import * as lib from "../../ignition/modules/lib"; 6 | import * as uniV2 from "../../ignition/modules/uniV2"; 7 | import UNIV2FACTORY from "../../test/helper/ext-abi/hardhat/uni-v2-factory.json"; 8 | import UNIV2ROUTER from "../../test/helper/ext-abi/hardhat/uni-v2-router.json"; 9 | 10 | import { Address, isAddress } from "viem"; 11 | 12 | dotenv.config(); 13 | 14 | function inferWeth() { 15 | const weth = process.env.WETH; 16 | 17 | if (process.env.PRODUCTION?.toLowerCase() == "true") { 18 | if (weth == undefined || weth == "") { 19 | throw new Error("WETH address not provided"); 20 | } 21 | 22 | if (isAddress(weth)) { 23 | throw new Error("Invalid WETH address"); 24 | } 25 | 26 | console.log("using WETH address from env:", weth); 27 | 28 | return weth; 29 | } 30 | 31 | return weth; 32 | } 33 | 34 | function inferBaseRedemptionFee() { 35 | const fee = process.env.PSM_BASE_REDEMPTION_FEE_PERCENTAGE; 36 | 37 | if (fee == undefined || fee == "") { 38 | throw new Error("PSM_BASE_REDEMPTION_FEE_PERCENTAGE not provided"); 39 | } 40 | 41 | return fee; 42 | } 43 | 44 | async function inferDeployer() { 45 | const deployer = await hre.viem.getWalletClients(); 46 | const pk = process.env.PRIVATE_KEY!; 47 | 48 | return { deployer: deployer[0], pk }; 49 | } 50 | 51 | async function main() { 52 | const { deployer, pk } = await inferDeployer(); 53 | 54 | console.log("PRODUCTION :", process.env.PRODUCTION); 55 | console.log("Network :", hre.network.name); 56 | console.log("Chain Id :", hre.network.config.chainId); 57 | console.log("Deployer :", deployer.account.address); 58 | console.log(""); 59 | 60 | const weth = 61 | inferWeth() ?? 62 | (await hre.ignition.deploy(uniV2.dummyWETH)).DummyWETH.address; 63 | const baseRedemptionFee = inferBaseRedemptionFee(); 64 | 65 | const { assetFactory } = await hre.ignition.deploy(core.assetFactory); 66 | console.log("AssetFactory deployed to :", assetFactory.address); 67 | 68 | const { CorkConfig } = await hre.ignition.deploy(core.corkConfig); 69 | console.log("CorkConfig deployed to :", CorkConfig.address); 70 | 71 | const { FlashSwapRouter } = await hre.ignition.deploy(core.flashSwapRouter); 72 | console.log("FlashSwapRouter deployed to :", FlashSwapRouter.address); 73 | 74 | const UniV2Factory = await hre.deployments.deploy("uniV2Factory", { 75 | from: pk, 76 | args: [deployer.account.address, FlashSwapRouter.address], 77 | contract: { 78 | abi: UNIV2FACTORY.abi, 79 | bytecode: UNIV2FACTORY.evm.bytecode.object, 80 | deployedBytecode: UNIV2FACTORY.evm.deployedBytecode.object, 81 | metadata: UNIV2FACTORY.metadata, 82 | }, 83 | gasLimit: 10_000_000, 84 | }); 85 | 86 | console.log("UniV2Factory deployed to :", UniV2Factory.address); 87 | 88 | const UniV2Router = await hre.deployments.deploy("uniV2Router", { 89 | from: pk, 90 | args: [UniV2Factory.address, weth, FlashSwapRouter.address], 91 | contract: { 92 | abi: UNIV2ROUTER.abi, 93 | bytecode: UNIV2ROUTER.evm.bytecode.object, 94 | deployedBytecode: UNIV2ROUTER.evm.deployedBytecode.object, 95 | metadata: UNIV2ROUTER.metadata, 96 | }, 97 | gasLimit: 10_000_000, 98 | }); 99 | 100 | console.log("UniV2Router deployed to :", UniV2Router.address); 101 | 102 | const { ModuleCore } = await hre.ignition.deploy(core.ModuleCore, { 103 | parameters: { 104 | ModuleCore: { 105 | assetFactory: assetFactory.address, 106 | uniV2Factory: UniV2Factory.address, 107 | flashSwapRouter: FlashSwapRouter.address, 108 | uniV2Router: UniV2Router.address, 109 | corkConfig: CorkConfig.address, 110 | psmBaseFeeRedemption: baseRedemptionFee, 111 | }, 112 | }, 113 | }); 114 | 115 | await assetFactory.write.initialize(); 116 | await FlashSwapRouter.write.initialize(); 117 | 118 | await assetFactory.write.transferOwnership([ModuleCore.address]); 119 | await FlashSwapRouter.write.transferOwnership([ModuleCore.address]); 120 | 121 | console.log("ModuleCore deployed to :", ModuleCore.address); 122 | } 123 | 124 | function sleep(ms: number) { 125 | return new Promise((resolve) => { 126 | setTimeout(resolve, ms); 127 | }); 128 | } 129 | main() 130 | .then(() => process.exit(0)) 131 | .catch((error) => { 132 | console.error(error); 133 | }); 134 | -------------------------------------------------------------------------------- /test/forge/CST.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.24; 2 | 3 | import "./Helper.sol"; 4 | import "./../../contracts/tokens/CST.sol"; 5 | import "./../../contracts/tokens/CETH.sol"; 6 | 7 | contract CSTTEST is Helper { 8 | CST public cst; 9 | CETH public ceth; 10 | 11 | uint256 defaultAmount = 200 ether; 12 | 13 | address secondUser = address(2); 14 | 15 | function setUp() external { 16 | ceth = new CETH("Cork Competition ETH", "CETH"); 17 | cst = new CST("Cork Staked Ethereum", "CST", address(ceth), DEFAULT_ADDRESS, 0, 0 ether); 18 | ceth.grantRole(ceth.MINTER_ROLE(), address(cst)); 19 | 20 | ceth.mint(secondUser, defaultAmount); 21 | ceth.mint(address(this), defaultAmount); 22 | } 23 | 24 | function test_rateIsCorrect() external { 25 | ceth.approve(address(cst), defaultAmount); 26 | cst.deposit(defaultAmount); 27 | vm.assertEq(cst.balanceOf(address(this)), defaultAmount); 28 | 29 | vm.prank(DEFAULT_ADDRESS); 30 | cst.changeRate(1 ether); 31 | 32 | // vm.assertEq(ceth.balanceOf(address(cst)), defaultAmount); 33 | 34 | // cst.approve(address(cst), defaultAmount); 35 | // cst.requestWithdrawal(defaultAmount); 36 | // cst.processWithdrawals(1); 37 | 38 | // vm.assertEq(ceth.balanceOf(address(this)), defaultAmount); 39 | 40 | vm.startPrank(secondUser); 41 | 42 | ceth.approve(address(cst), defaultAmount); 43 | cst.deposit(defaultAmount); 44 | vm.assertEq(cst.balanceOf(secondUser), defaultAmount); 45 | 46 | cst.approve(address(cst), defaultAmount); 47 | cst.requestWithdrawal(defaultAmount); 48 | cst.processWithdrawals(1); 49 | 50 | vm.assertEq(ceth.balanceOf(secondUser), defaultAmount); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /test/forge/POC.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.24; 2 | 3 | import "./../../contracts/core/flash-swaps/FlashSwapRouter.sol"; 4 | import {Helper} from "./Helper.sol"; 5 | import {DummyWETH} from "./../../contracts/dummy/DummyWETH.sol"; 6 | import "./../../contracts/core/assets/Asset.sol"; 7 | import {Id, Pair, PairLibrary} from "./../../contracts/libraries/Pair.sol"; 8 | import "./../../contracts/interfaces/IPSMcore.sol"; 9 | import "forge-std/console.sol"; 10 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 11 | import "./../../contracts/libraries/MathHelper.sol"; 12 | 13 | contract POCTest is Helper { 14 | DummyWETH internal ra; 15 | DummyWETH internal pa; 16 | Id public currencyId; 17 | 18 | uint256 public DEFAULT_DEPOSIT_AMOUNT = 10000 ether; 19 | uint256 redemptionFeePercentage = 5 ether; 20 | 21 | uint256 public dsId; 22 | 23 | address public lv; 24 | address user2 = address(30); 25 | address ds; 26 | uint256 _expiry = 1 days; 27 | 28 | function setUp() public { 29 | vm.startPrank(DEFAULT_ADDRESS); 30 | 31 | deployModuleCore(); 32 | 33 | (ra, pa, currencyId) = initializeAndIssueNewDs(_expiry, redemptionFeePercentage); 34 | vm.deal(DEFAULT_ADDRESS, type(uint256).max); 35 | 36 | ra.deposit{value: type(uint128).max}(); 37 | pa.deposit{value: type(uint128).max}(); 38 | 39 | // 10000 for psm 10000 for LV 40 | ra.approve(address(moduleCore), type(uint256).max); 41 | 42 | moduleCore.depositPsm(currencyId, DEFAULT_DEPOSIT_AMOUNT); 43 | 44 | // save initial data 45 | address exchangeRateProvider = address(corkConfig.defaultExchangeRateProvider()); 46 | lv = assetFactory.getLv(address(ra), address(pa), DEFAULT_INITIAL_DS_PRICE, _expiry, exchangeRateProvider); 47 | dsId = moduleCore.lastDsId(currencyId); 48 | (, ds) = moduleCore.swapAsset(currencyId, 1); 49 | Asset(ds).approve(address(moduleCore), type(uint256).max); 50 | pa.approve(address(moduleCore), type(uint256).max); 51 | } 52 | 53 | function test_POC() external {} 54 | } 55 | -------------------------------------------------------------------------------- /test/forge/SetupTest.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.24; 2 | 3 | import {Helper} from "./Helper.sol"; 4 | 5 | contract SetupTest is Helper { 6 | function test_setupModuleCore() public { 7 | vm.startPrank(DEFAULT_ADDRESS); 8 | deployModuleCore(); 9 | vm.stopPrank(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/forge/SwapperMath.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.24; 2 | 3 | import {SwapperMathLibrary} from "./../../contracts/libraries/DsSwapperMathLib.sol"; 4 | import "forge-std/Test.sol"; 5 | import "forge-std/console.sol"; 6 | import "./../../contracts/libraries/uni-v2/UniswapV2Library.sol"; 7 | import {UD60x18, convert, add, mul, pow, sub, div, unwrap, ud} from "@prb/math/src/UD60x18.sol"; 8 | 9 | // TODO : adjust tests 10 | 11 | contract SwapMathTest is Test { 12 | function test_calculateDecay() external { 13 | // 1 % 14 | UD60x18 decayDiscountInDays = convert(1); 15 | 16 | // together make 1 day worth of discount 17 | UD60x18 issuanceTime = convert(2 days); 18 | UD60x18 currentTime = convert(3 days); 19 | 20 | UD60x18 discount = SwapperMathLibrary.calculateDecayDiscount(decayDiscountInDays, issuanceTime, currentTime); 21 | 22 | vm.assertApproxEqAbs(unwrap(discount), 99e18, 0.0000001 ether); 23 | } 24 | 25 | function test_calculateRolloverSale() external { 26 | uint256 lvReserve = 100 ether; 27 | uint256 psmReserve = 100 ether; 28 | uint256 raProvided = 1 ether; 29 | uint256 hiya = 0.1111111111 ether; 30 | 31 | ( 32 | uint256 lvProfit, 33 | uint256 psmProfit, 34 | uint256 raLeft, 35 | uint256 dsReceived, 36 | uint256 lvReserveUsed, 37 | uint256 psmReserveUsed 38 | ) = SwapperMathLibrary.calculateRolloverSale(lvReserve, psmReserve, raProvided, hiya); 39 | 40 | vm.assertApproxEqAbs(lvProfit, 0.5 ether, 0.0001 ether); 41 | vm.assertApproxEqAbs(psmProfit, 0.5 ether, 0.0001 ether); 42 | vm.assertApproxEqAbs(raLeft, 0 ether, 0.0001 ether); 43 | vm.assertApproxEqAbs(dsReceived, 10 ether, 0.0001 ether); 44 | vm.assertApproxEqAbs(lvReserveUsed, 5 ether, 0.0001 ether); 45 | vm.assertApproxEqAbs(psmReserveUsed, 5 ether, 0.0001 ether); 46 | 47 | // 25% profit for lv 48 | lvReserve = 50 ether; 49 | // 75% profit for psm 50 | psmReserve = 150 ether; 51 | 52 | (lvProfit, psmProfit, raLeft, dsReceived, lvReserveUsed, psmReserveUsed) = 53 | SwapperMathLibrary.calculateRolloverSale(lvReserve, psmReserve, raProvided, hiya); 54 | 55 | vm.assertApproxEqAbs(lvProfit, 0.25 ether, 0.0001 ether); 56 | vm.assertApproxEqAbs(psmProfit, 0.75 ether, 0.0001 ether); 57 | vm.assertApproxEqAbs(raLeft, 0 ether, 0.0001 ether); 58 | vm.assertApproxEqAbs(dsReceived, 10 ether, 0.0001 ether); 59 | vm.assertApproxEqAbs(lvReserveUsed, 2.5 ether, 0.0001 ether); 60 | vm.assertApproxEqAbs(psmReserveUsed, 7.5 ether, 0.0001 ether); 61 | } 62 | 63 | function test_sellDs() external { 64 | uint256 ctReserve = 1000 ether; 65 | uint256 raReserve = 900 ether; 66 | 67 | uint256 amountToSell = 5 ether; 68 | 69 | uint256 borrwedAmount = 4.527164981 ether; 70 | 71 | (bool success, uint256 raReceived) = SwapperMathLibrary.getAmountOutSellDs(borrwedAmount, amountToSell); 72 | 73 | vm.assertEq(success, true); 74 | vm.assertApproxEqAbs(raReceived, 0.472835019 ether, 0.000001 ether); 75 | 76 | // can't sell if CT > RA 77 | amountToSell = 4 ether; 78 | (success, raReceived) = SwapperMathLibrary.getAmountOutSellDs(borrwedAmount, amountToSell); 79 | 80 | vm.assertEq(success, false); 81 | } 82 | 83 | function testFuzz_sellDs(uint256 amountToSell, uint256 repaymentAmount) external { 84 | vm.assume(amountToSell > repaymentAmount); 85 | 86 | (bool success, uint256 raReceived) = SwapperMathLibrary.getAmountOutSellDs(repaymentAmount, amountToSell); 87 | 88 | vm.assertTrue(success); 89 | } 90 | 91 | function test_calculateDsExtraFee() external { 92 | uint256 amount = 10 ether; 93 | uint256 reserveSellPercentage = 50 ether; 94 | uint256 extraFeePercentage = 30 ether; 95 | 96 | uint256 result = SwapperMathLibrary.calculateDsExtraFee(amount, reserveSellPercentage, extraFeePercentage); 97 | 98 | vm.assertEq(result, 1.5 ether); 99 | } 100 | 101 | function test_calculateDynamicSellPressure() external { 102 | // 1.5 % risk premium where 1 = 100% 103 | uint256 currentRiskPremium = 0.015 ether; 104 | 105 | // 3% threshold where 100 = 100% 106 | uint256 threshold = 3 ether; 107 | 108 | uint256 percentage = 109 | SwapperMathLibrary.calculateOptimalSellPressureWithRiskPremium(ud(currentRiskPremium), ud(threshold)); 110 | // should be 50% 111 | vm.assertEq(percentage, 50 ether); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /test/forge/TestFlashSwapRouter.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.24; 2 | 3 | import "./../../contracts/core/flash-swaps/FlashSwapRouter.sol"; 4 | 5 | /// @title TestFlashSwapRouter Contract, used for testing FlashSwapRouter contract, mostly here for getter functions 6 | contract TestFlashSwapRouter is RouterState { 7 | constructor() RouterState() {} 8 | 9 | // ------------------------------------------------------------ Getters ------------------------------ 10 | function getAssetPair(Id id, uint256 dsId) external view returns (AssetPair memory) { 11 | return reserves[id].ds[dsId]; 12 | } 13 | 14 | function getReserveSellPressurePercentageThresold(Id id) external view returns (uint256) { 15 | return reserves[id].reserveSellPressurePercentageThreshold; 16 | } 17 | 18 | function getHiyaCumulated(Id id) external view returns (uint256) { 19 | return reserves[id].hiyaCumulated; 20 | } 21 | 22 | function getVhiyaCumulated(Id id) external view returns (uint256) { 23 | return reserves[id].vhiyaCumulated; 24 | } 25 | 26 | function getDecayDiscountRateInDays(Id id) external view returns (uint256) { 27 | return reserves[id].decayDiscountRateInDays; 28 | } 29 | 30 | function getRolloverEndInBlockNumber(Id id) external view returns (uint256) { 31 | return reserves[id].rolloverEndInBlockNumber; 32 | } 33 | 34 | function getHiya(Id id) external view returns (uint256) { 35 | return reserves[id].hiya; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /test/forge/TestModuleCore.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.24; 2 | 3 | import {ModuleCore} from "./../../contracts/core/ModuleCore.sol"; 4 | import {Id, Pair} from "../../contracts/libraries/Pair.sol"; 5 | import { 6 | State, 7 | PsmPoolArchive, 8 | VaultState, 9 | VaultAmmLiquidityPool, 10 | Balances, 11 | VaultConfig, 12 | VaultBalances, 13 | VaultWithdrawalPool 14 | } from "../../contracts/libraries/State.sol"; 15 | import {BitMaps} from "@openzeppelin/contracts/utils/structs/BitMaps.sol"; 16 | import {DepegSwap} from "../../contracts/libraries/DepegSwapLib.sol"; 17 | import {LvAsset} from "../../contracts/libraries/LvAssetLib.sol"; 18 | import "./../../contracts/core/StateView.sol"; 19 | 20 | /// @title TestModuleCore Contract, used for testing ModuleCore contract, mostly here for getter functions 21 | contract TestModuleCore is ModuleCore, StateView {} 22 | -------------------------------------------------------------------------------- /test/forge/Vault.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.24; 2 | 3 | import "./../../contracts/core/flash-swaps/FlashSwapRouter.sol"; 4 | import {Helper} from "./Helper.sol"; 5 | import {DummyWETH} from "./../../contracts/dummy/DummyWETH.sol"; 6 | import "./../../contracts/core/assets/Asset.sol"; 7 | import {Id, Pair, PairLibrary} from "./../../contracts/libraries/Pair.sol"; 8 | import "./../../contracts/interfaces/IPSMcore.sol"; 9 | import "forge-std/console.sol"; 10 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 11 | 12 | contract VaultRedeemTest is Helper { 13 | DummyWETH internal ra; 14 | DummyWETH internal pa; 15 | Id public currencyId; 16 | 17 | uint256 public DEFAULT_DEPOSIT_AMOUNT = 10000 ether; 18 | 19 | uint256 public dsId; 20 | 21 | address public lv; 22 | address user2 = address(30); 23 | uint256 _expiry = 1 days; 24 | 25 | function setUp() public { 26 | vm.startPrank(DEFAULT_ADDRESS); 27 | 28 | deployModuleCore(); 29 | 30 | (ra, pa, currencyId) = initializeAndIssueNewDs(_expiry, 1 ether); 31 | vm.deal(DEFAULT_ADDRESS, type(uint256).max); 32 | 33 | ra.deposit{value: type(uint128).max}(); 34 | pa.deposit{value: type(uint128).max}(); 35 | 36 | vm.stopPrank(); 37 | vm.startPrank(user2); 38 | 39 | vm.deal(user2, type(uint256).max); 40 | ra.deposit{value: type(uint128).max}(); 41 | pa.deposit{value: type(uint128).max}(); 42 | 43 | vm.stopPrank(); 44 | vm.startPrank(DEFAULT_ADDRESS); 45 | 46 | // 10000 for psm 10000 for LV 47 | ra.approve(address(moduleCore), type(uint256).max); 48 | 49 | // moduleCore.depositPsm(currencyId, DEFAULT_DEPOSIT_AMOUNT); 50 | moduleCore.depositLv(currencyId, DEFAULT_DEPOSIT_AMOUNT, 0, 0); 51 | 52 | // save initial data 53 | address exchangeRateProvider = address(corkConfig.defaultExchangeRateProvider()); 54 | lv = assetFactory.getLv(address(ra), address(pa), DEFAULT_INITIAL_DS_PRICE, _expiry, exchangeRateProvider); 55 | dsId = moduleCore.lastDsId(currencyId); 56 | } 57 | 58 | function test_reissueMany() external { 59 | // wont' work because of the gas limit, so we ignore gas for this 60 | vm.pauseGasMetering(); 61 | 62 | for (uint256 i = 0; i < 100; i++) { 63 | ff_expired(); 64 | } 65 | } 66 | 67 | function defaultExchangeRate() internal pure override returns (uint256) { 68 | return 1.5 ether; 69 | } 70 | 71 | function ff_expired() internal { 72 | dsId = moduleCore.lastDsId(currencyId); 73 | (address ct,) = moduleCore.swapAsset(currencyId, dsId); 74 | uint256 expiry = Asset(ct).expiry(); 75 | 76 | vm.warp(expiry); 77 | 78 | issueNewDs(currencyId); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /test/forge/integration/config/CircuitBreakerUpdate.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.24; 2 | 3 | import "./../../Helper.sol"; 4 | import "./../../../../contracts/libraries/State.sol"; 5 | import "./../../../../contracts/dummy/DummyWETH.sol"; 6 | 7 | contract CircuiBreakerUpdate is Helper { 8 | DummyWETH ra; 9 | DummyWETH pa; 10 | 11 | function setUp() external { 12 | vm.warp(10 days); 13 | 14 | vm.startPrank(DEFAULT_ADDRESS); 15 | deployModuleCore(); 16 | (ra, pa,) = initializeAndIssueNewDs(block.timestamp + 10 days); 17 | 18 | vm.deal(DEFAULT_ADDRESS, 1_000_000_000 ether); 19 | ra.deposit{value: 1_000_000_000 ether}(); 20 | ra.approve(address(moduleCore), 1_000_000_000 ether); 21 | 22 | vm.deal(DEFAULT_ADDRESS, 1_000_000 ether); 23 | pa.deposit{value: 1_000_000 ether}(); 24 | pa.approve(address(moduleCore), 1_000_000 ether); 25 | } 26 | 27 | function test_shouldUpdateCircuitBreaker() external { 28 | // dummy deposit to get nav 29 | moduleCore.depositLv(defaultCurrencyId, 100 ether, 0, 0); 30 | 31 | VaultConfig memory config = moduleCore.getVaultConfig(defaultCurrencyId); 32 | 33 | vm.assertNotEq(config.navCircuitBreaker.lastUpdate0, 0); 34 | vm.assertEq(config.navCircuitBreaker.lastUpdate1, 0); 35 | 36 | vm.assertNotEq(config.navCircuitBreaker.snapshot0, 0); 37 | vm.assertEq(config.navCircuitBreaker.snapshot1, 0); 38 | 39 | vm.warp(block.timestamp + 1 days); 40 | 41 | corkConfig.forceUpdateNavCircuitBreakerReferenceValue(defaultCurrencyId); 42 | 43 | config = moduleCore.getVaultConfig(defaultCurrencyId); 44 | 45 | vm.assertNotEq(config.navCircuitBreaker.lastUpdate0, 0); 46 | vm.assertNotEq(config.navCircuitBreaker.lastUpdate1, 0); 47 | 48 | vm.assertNotEq(config.navCircuitBreaker.snapshot0, 0); 49 | vm.assertNotEq(config.navCircuitBreaker.snapshot1, 0); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /test/forge/integration/config/Liquidator.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.24; 2 | 3 | import "./../../Helper.sol"; 4 | 5 | contract liquidatorRoleTest is Helper { 6 | uint256 amount = 1 ether; 7 | 8 | address constant DUMMY_LIQUIDATOR_ADDRESS = address(420); 9 | address constant DUMMY_USER_ADDRESS = address(69); 10 | 11 | function setUp() external { 12 | vm.startPrank(DEFAULT_ADDRESS); 13 | 14 | deployModuleCore(); 15 | } 16 | 17 | function test_grantLiquidator() external { 18 | corkConfig.grantLiquidatorRole(DUMMY_LIQUIDATOR_ADDRESS, DUMMY_USER_ADDRESS); 19 | bool result = corkConfig.isTrustedLiquidationExecutor(DUMMY_LIQUIDATOR_ADDRESS, DUMMY_USER_ADDRESS); 20 | 21 | vm.assertEq(result, true); 22 | } 23 | 24 | function test_revokeLiquidator() external { 25 | corkConfig.grantLiquidatorRole(DUMMY_LIQUIDATOR_ADDRESS, DUMMY_USER_ADDRESS); 26 | 27 | bool result = corkConfig.isTrustedLiquidationExecutor(DUMMY_LIQUIDATOR_ADDRESS, DUMMY_USER_ADDRESS); 28 | 29 | vm.assertEq(result, true); 30 | 31 | corkConfig.revokeLiquidatorRole(DUMMY_LIQUIDATOR_ADDRESS, DUMMY_USER_ADDRESS); 32 | 33 | result = corkConfig.isTrustedLiquidationExecutor(DUMMY_LIQUIDATOR_ADDRESS, DUMMY_USER_ADDRESS); 34 | 35 | vm.assertEq(result, false); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /test/forge/integration/config/RateUpdate.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.24; 2 | 3 | import "./../../Helper.sol"; 4 | import "@openzeppelin/contracts/interfaces/IERC20Metadata.sol"; 5 | import "./../../../../contracts/core/assets/Asset.sol"; 6 | 7 | contract RateUpdateTest is Helper { 8 | function setUp() external { 9 | vm.startPrank(DEFAULT_ADDRESS); 10 | deployModuleCore(); 11 | initializeAndIssueNewDs(100); 12 | } 13 | 14 | function test_shouldUpdateRateDownCorrectly() external { 15 | uint256 rate = moduleCore.exchangeRate(defaultCurrencyId); 16 | 17 | vm.assertEq(rate, 1 ether); 18 | 19 | corkConfig.updatePsmRate(defaultCurrencyId, 0.9 ether); 20 | 21 | rate = moduleCore.exchangeRate(defaultCurrencyId); 22 | 23 | vm.assertEq(rate, 0.9 ether); 24 | } 25 | 26 | function test_shouldUpdateRateUpCorrectlyOnNewIssuance() external { 27 | uint256 rate = moduleCore.exchangeRate(defaultCurrencyId); 28 | 29 | vm.assertEq(rate, 1 ether); 30 | 31 | initializeAndIssueNewDs(1000); 32 | 33 | corkConfig.updatePsmRate(defaultCurrencyId, 1.1 ether); 34 | 35 | rate = moduleCore.exchangeRate(defaultCurrencyId); 36 | 37 | vm.assertEq(rate, 1 ether); 38 | 39 | ff_expired(); 40 | 41 | rate = moduleCore.exchangeRate(defaultCurrencyId); 42 | 43 | vm.assertEq(rate, 1.1 ether); 44 | } 45 | 46 | // ff to expiry and update infos 47 | function ff_expired() internal { 48 | (, address ds) = moduleCore.swapAsset(defaultCurrencyId, 1); 49 | 50 | // fast forward to expiry 51 | uint256 expiry = Asset(ds).expiry(); 52 | vm.warp(expiry); 53 | issueNewDs(defaultCurrencyId); 54 | } 55 | 56 | function test_ShouldNotUpdateRateUpCorrectlyOnActive() external { 57 | uint256 rate = moduleCore.exchangeRate(defaultCurrencyId); 58 | 59 | vm.assertEq(rate, 1 ether); 60 | 61 | corkConfig.updatePsmRate(defaultCurrencyId, 1.1 ether); 62 | 63 | rate = moduleCore.exchangeRate(defaultCurrencyId); 64 | 65 | vm.assertEq(rate, 1 ether); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /test/forge/integration/config/Whitelist.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.24; 2 | 3 | import "./../../Helper.sol"; 4 | 5 | contract whitelistTest is Helper { 6 | uint256 amount = 1 ether; 7 | 8 | address constant DUMMY_LIQUIDATOR_ADDRESS = address(420); 9 | 10 | function setUp() external { 11 | vm.startPrank(DEFAULT_ADDRESS); 12 | 13 | deployModuleCore(); 14 | } 15 | 16 | function test_whitelist() external { 17 | corkConfig.whitelist(DUMMY_LIQUIDATOR_ADDRESS); 18 | 19 | vm.warp(block.timestamp + 7 days); 20 | 21 | bool isWhitelisted = corkConfig.isLiquidationWhitelisted(DUMMY_LIQUIDATOR_ADDRESS); 22 | vm.assertEq(isWhitelisted, true); 23 | } 24 | 25 | function test_notWhitelistBefore7Days() external { 26 | corkConfig.whitelist(DUMMY_LIQUIDATOR_ADDRESS); 27 | 28 | bool isWhitelisted = corkConfig.isLiquidationWhitelisted(DUMMY_LIQUIDATOR_ADDRESS); 29 | vm.assertEq(isWhitelisted, false); 30 | } 31 | 32 | function test_blackList() external { 33 | corkConfig.whitelist(DUMMY_LIQUIDATOR_ADDRESS); 34 | 35 | vm.warp(block.timestamp + 7 days); 36 | 37 | corkConfig.blacklist(DUMMY_LIQUIDATOR_ADDRESS); 38 | 39 | bool isWhitelisted = corkConfig.isLiquidationWhitelisted(DUMMY_LIQUIDATOR_ADDRESS); 40 | vm.assertEq(isWhitelisted, false); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /test/forge/integration/router/sell/SellDs.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.24; 2 | 3 | import "../../../../../contracts/core/flash-swaps/FlashSwapRouter.sol"; 4 | import {Helper} from "../../../Helper.sol"; 5 | import {DummyWETH} from "../../../../../contracts/dummy/DummyWETH.sol"; 6 | import "../../../../../contracts/core/assets/Asset.sol"; 7 | import {Id, Pair, PairLibrary} from "../../../../../contracts/libraries/Pair.sol"; 8 | import "../../../../../contracts/interfaces/IPSMcore.sol"; 9 | import "../../../../../contracts/interfaces/IDsFlashSwapRouter.sol"; 10 | import "forge-std/console.sol"; 11 | 12 | contract SellDsTest is Helper { 13 | DummyWETH internal ra; 14 | DummyWETH internal pa; 15 | address ct; 16 | address ds; 17 | Id public currencyId; 18 | 19 | // we double the amount(should be 2050) since we're splitting CT when user deposit RA to the test default(50%) 20 | uint256 public DEFAULT_DEPOSIT_AMOUNT = 4100 ether; 21 | 22 | uint256 end = block.timestamp + 10 days; 23 | uint256 current = block.timestamp + 1 days; 24 | 25 | uint256 public dsId; 26 | 27 | function defaultInitialArp() internal pure virtual override returns (uint256) { 28 | return 5 ether; 29 | } 30 | 31 | function defaultExchangeRate() internal pure virtual override returns (uint256) { 32 | return 1.1 ether; 33 | } 34 | 35 | function setUp() public virtual { 36 | vm.startPrank(DEFAULT_ADDRESS); 37 | 38 | deployModuleCore(); 39 | 40 | (ra, pa, currencyId) = initializeAndIssueNewDs(end); 41 | 42 | vm.deal(DEFAULT_ADDRESS, 100_000_000_000 ether); 43 | 44 | ra.deposit{value: 1_000_000_000 ether}(); 45 | pa.deposit{value: 1_000_000_000 ether}(); 46 | 47 | ra.approve(address(moduleCore), 100_000_000_000 ether); 48 | 49 | moduleCore.depositPsm(currencyId, DEFAULT_DEPOSIT_AMOUNT); 50 | moduleCore.depositLv(currencyId, DEFAULT_DEPOSIT_AMOUNT, 0, 0); 51 | 52 | dsId = moduleCore.lastDsId(currencyId); 53 | (ct, ds) = moduleCore.swapAsset(currencyId, dsId); 54 | } 55 | 56 | function test_sellDS() public virtual { 57 | ra.approve(address(flashSwapRouter), type(uint256).max); 58 | 59 | uint256 amount = 0.5 ether; 60 | 61 | Asset(ds).approve(address(flashSwapRouter), amount); 62 | 63 | uint256 balanceRaBefore = ra.balanceOf(DEFAULT_ADDRESS); 64 | vm.warp(current); 65 | 66 | // TODO : figure out the out of whack gas consumption 67 | vm.pauseGasMetering(); 68 | corkConfig.updateAmmBaseFeePercentage(defaultCurrencyId, 1 ether); 69 | 70 | uint256 amountOut = flashSwapRouter.swapDsforRa(currencyId, dsId, amount, 0); 71 | 72 | uint256 balanceRaAfter = ra.balanceOf(DEFAULT_ADDRESS); 73 | 74 | vm.assertEq(balanceRaAfter - balanceRaBefore, amountOut); 75 | } 76 | 77 | function testFuzz_basicSanityTest(uint256 amount) external { 78 | // only possible up to 50e18 without fee since the reserve is 1000:1050(RA:CT) 79 | amount = bound(amount, 0.0001 ether, 50 ether); 80 | 81 | Asset(ds).approve(address(flashSwapRouter), amount); 82 | 83 | uint256 balanceRaBefore = ra.balanceOf(DEFAULT_ADDRESS); 84 | vm.warp(current); 85 | 86 | // TODO : figure out the out of whack gas consumption 87 | vm.pauseGasMetering(); 88 | 89 | uint256 amountOut = flashSwapRouter.swapDsforRa(currencyId, dsId, amount, 0); 90 | 91 | uint256 balanceRaAfter = ra.balanceOf(DEFAULT_ADDRESS); 92 | 93 | vm.assertEq(balanceRaAfter - balanceRaBefore, amountOut); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /test/forge/integration/vault/LiquidityFunds.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.24; 2 | 3 | import "./../../../../contracts/core/flash-swaps/FlashSwapRouter.sol"; 4 | import {Helper} from "./../../Helper.sol"; 5 | import {DummyWETH} from "./../../../../contracts/dummy/DummyWETH.sol"; 6 | import "./../../../../contracts/core/assets/Asset.sol"; 7 | import {Id, Pair, PairLibrary} from "./../../../../contracts/libraries/Pair.sol"; 8 | import "./../../../../contracts/interfaces/IPSMcore.sol"; 9 | import "forge-std/console.sol"; 10 | import "./../../../../contracts/interfaces/IErrors.sol"; 11 | import "./../../../../contracts/interfaces/IErrors.sol"; 12 | 13 | contract VaultLiquidityFundsTest is Helper { 14 | DummyWETH internal ra; 15 | DummyWETH internal pa; 16 | Id public currencyId; 17 | 18 | uint256 public DEFAULT_DEPOSIT_AMOUNT = 2050 ether; 19 | 20 | uint256 public dsId; 21 | 22 | address public ct; 23 | address public ds; 24 | 25 | function setUp() public { 26 | vm.startPrank(DEFAULT_ADDRESS); 27 | 28 | deployModuleCore(); 29 | 30 | (ra, pa, currencyId) = initializeAndIssueNewDs(block.timestamp + 70 days); 31 | vm.deal(DEFAULT_ADDRESS, 100_000_000 ether); 32 | ra.deposit{value: 100000 ether}(); 33 | pa.deposit{value: 100000 ether}(); 34 | 35 | // 10000 for psm 10000 for LV 36 | ra.approve(address(moduleCore), 100_000_000 ether); 37 | pa.approve(address(moduleCore), 100_000_000 ether); 38 | 39 | corkConfig.updateLvStrategyCtSplitPercentage(currencyId, 50 ether); 40 | 41 | moduleCore.depositPsm(currencyId, DEFAULT_DEPOSIT_AMOUNT); 42 | moduleCore.depositLv(currencyId, DEFAULT_DEPOSIT_AMOUNT, 0, 0); 43 | 44 | corkConfig.whitelist(DEFAULT_ADDRESS); 45 | vm.warp(block.timestamp + 10 days); 46 | 47 | // save initial data 48 | fetchProtocolGeneralInfo(); 49 | } 50 | 51 | function fetchProtocolGeneralInfo() internal { 52 | dsId = moduleCore.lastDsId(currencyId); 53 | (ct, ds) = moduleCore.swapAsset(currencyId, dsId); 54 | } 55 | 56 | // ff to expiry and update infos 57 | function ff_expired() internal { 58 | // fast forward to expiry 59 | uint256 expiry = Asset(ds).expiry(); 60 | vm.warp(expiry); 61 | 62 | uint256 rolloverBlocks = flashSwapRouter.getRolloverEndInBlockNumber(currencyId); 63 | vm.roll(block.number + rolloverBlocks); 64 | 65 | Asset(ct).approve(address(moduleCore), DEFAULT_DEPOSIT_AMOUNT); 66 | 67 | issueNewDs(currencyId); 68 | } 69 | 70 | function test_revertNoFunds() external { 71 | uint256 fundsAvailable = moduleCore.liquidationFundsAvailable(currencyId); 72 | 73 | vm.assertEq(fundsAvailable, 0); 74 | 75 | vm.expectRevert(IErrors.InsufficientFunds.selector); 76 | moduleCore.requestLiquidationFunds(currencyId, 1 ether); 77 | } 78 | 79 | function test_requestAfterExpiries() external { 80 | // we redeem 1000 RA first first 81 | Asset(ds).approve(address(moduleCore), 1000 ether); 82 | moduleCore.redeemRaWithDsPa(currencyId, dsId, 1000 ether); 83 | 84 | ff_expired(); 85 | 86 | uint256 fundsAvailable = moduleCore.liquidationFundsAvailable(currencyId); 87 | 88 | vm.assertTrue(fundsAvailable > 0); 89 | 90 | moduleCore.requestLiquidationFunds(currencyId, fundsAvailable); 91 | 92 | fundsAvailable = moduleCore.liquidationFundsAvailable(currencyId); 93 | vm.assertEq(fundsAvailable, 0); 94 | } 95 | 96 | function test_revertNotWhiteListed() external { 97 | vm.stopPrank(); 98 | 99 | vm.expectRevert(IErrors.OnlyWhiteListed.selector); 100 | moduleCore.requestLiquidationFunds(currencyId, 1 ether); 101 | } 102 | 103 | function test_receiveFunds() external { 104 | uint256 raBalanceBefore = ra.balanceOf(address(moduleCore)); 105 | 106 | uint256 amount = 1000 ether; 107 | ra.approve(address(moduleCore), amount); 108 | 109 | moduleCore.receiveTradeExecuctionResultFunds(currencyId, amount); 110 | 111 | uint256 raBalanceAfter = ra.balanceOf(address(moduleCore)); 112 | 113 | vm.assertEq(raBalanceAfter - raBalanceBefore, amount); 114 | } 115 | 116 | function test_useFundsAfterReceive() external { 117 | uint256 amount = 1000 ether; 118 | ra.approve(address(moduleCore), amount); 119 | 120 | moduleCore.receiveTradeExecuctionResultFunds(currencyId, amount); 121 | 122 | uint256 tradeFundsAvailable = moduleCore.tradeExecutionFundsAvailable(currencyId); 123 | 124 | vm.assertEq(tradeFundsAvailable, 1000 ether); 125 | 126 | corkConfig.useVaultTradeExecutionResultFunds(currencyId); 127 | 128 | tradeFundsAvailable = moduleCore.tradeExecutionFundsAvailable(currencyId); 129 | vm.assertEq(tradeFundsAvailable, 0); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /test/forge/unit/Asset.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.26; 2 | 3 | import "./../Helper.sol"; 4 | import "./../../../contracts/dummy/DummyWETH.sol"; 5 | import "./../../../contracts/core/assets/Asset.sol"; 6 | import "./../../../contracts/libraries/Pair.sol"; 7 | 8 | contract AssetTest is Helper { 9 | DummyWETH ra; 10 | DummyWETH pa; 11 | Asset ct; 12 | Asset ds; 13 | Asset lv; 14 | 15 | uint256 public constant depositAmount = 1 ether; 16 | uint256 public constant redeemAmount = 0.5 ether; 17 | 18 | function setUp() external { 19 | vm.startPrank(DEFAULT_ADDRESS); 20 | 21 | deployModuleCore(); 22 | 23 | (ra, pa,) = initializeAndIssueNewDs(block.timestamp + 1 days); 24 | vm.deal(DEFAULT_ADDRESS, 100_000_000_000 ether); 25 | ra.deposit{value: 1_000_000_000 ether}(); 26 | pa.deposit{value: 1_000_000_000 ether}(); 27 | 28 | ra.approve(address(moduleCore), 100_000_000_000 ether); 29 | pa.approve(address(moduleCore), 100_000_000_000 ether); 30 | 31 | moduleCore.depositPsm(defaultCurrencyId, depositAmount); 32 | 33 | (address _ct, address _ds) = moduleCore.swapAsset(defaultCurrencyId, 1); 34 | ct = Asset(_ct); 35 | ds = Asset(_ds); 36 | lv = Asset(moduleCore.lvAsset(defaultCurrencyId)); 37 | } 38 | 39 | function fetchProtocolGeneralInfo() internal { 40 | uint256 dsId = moduleCore.lastDsId(defaultCurrencyId); 41 | (address _ct, address _ds) = moduleCore.swapAsset(defaultCurrencyId, dsId); 42 | ct = Asset(_ct); 43 | ds = Asset(_ds); 44 | ds.approve(address(moduleCore), 100_000_000_000 ether); 45 | } 46 | 47 | function assertReserve(Asset token, uint256 expectedRa, uint256 expectedPa) internal { 48 | (uint256 ra, uint256 pa) = token.getReserves(); 49 | 50 | vm.assertEq(ra, expectedRa); 51 | vm.assertEq(pa, expectedPa); 52 | } 53 | 54 | function testResolution() external { 55 | // ct, should return current reserve 56 | assertReserve(ct, depositAmount, 0); 57 | 58 | // ds, should return current reserve 59 | assertReserve(ds, depositAmount, 0); 60 | 61 | assertReserve(lv, depositAmount, 0); 62 | 63 | // fast forward to expiry 64 | uint256 expiry = ds.expiry(); 65 | vm.warp(expiry); 66 | 67 | issueNewDs(defaultCurrencyId); 68 | 69 | assertReserve(ct, depositAmount, 0); 70 | assertReserve(ds, depositAmount, 0); 71 | assertReserve(lv, 0, 0); 72 | 73 | Asset(ct).approve(address(moduleCore), type(uint128).max); 74 | moduleCore.rolloverExpiredCt(defaultCurrencyId, depositAmount, 1); 75 | 76 | assertReserve(ct, 0, 0); 77 | assertReserve(ds, 0, 0); 78 | assertReserve(lv, depositAmount, 0); 79 | 80 | fetchProtocolGeneralInfo(); 81 | 82 | assertReserve(ct, depositAmount, 0); 83 | assertReserve(ds, depositAmount, 0); 84 | assertReserve(lv, depositAmount, 0); 85 | 86 | moduleCore.redeemRaWithDsPa(defaultCurrencyId, 2, redeemAmount); 87 | 88 | assertReserve(ct, redeemAmount, redeemAmount); 89 | assertReserve(ds, redeemAmount, redeemAmount); 90 | assertReserve(lv, redeemAmount, redeemAmount); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /test/forge/unit/LpParser.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.24; 3 | 4 | import "forge-std/Test.sol"; 5 | import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; 6 | import {LpParser} from "./../../../contracts/libraries/LpSymbolParser.sol"; 7 | 8 | contract LpSymbolParserTest is Test { 9 | function testFuzz_parse(address expectedRa, address expectedCt) external pure { 10 | string memory identifier = 11 | string.concat("LP-", Strings.toChecksumHexString(expectedRa), "-", Strings.toChecksumHexString(expectedCt)); 12 | 13 | (address ra, address ct) = LpParser.parse(identifier); 14 | 15 | assertEq(ra, expectedRa); 16 | assertEq(ct, expectedCt); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test/forge/unit/TransferHelper.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.24; 3 | 4 | import "forge-std/Test.sol"; 5 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 6 | import "./../Helper.sol"; 7 | import "./../../../contracts/libraries/TransferHelper.sol"; 8 | 9 | contract MockERC20 is ERC20 { 10 | uint8 private _decimals; 11 | 12 | constructor(string memory name_, string memory symbol_, uint8 decimals_) ERC20(name_, symbol_) { 13 | _decimals = decimals_; 14 | _mint(msg.sender, 10000 * (10 ** uint256(_decimals))); 15 | } 16 | 17 | function decimals() public view override returns (uint8) { 18 | return _decimals; 19 | } 20 | } 21 | 22 | contract TransferHelperTest is Helper { 23 | MockERC20 token18; 24 | MockERC20 token6; 25 | address user = address(0x123); 26 | address recipient = address(0x456); 27 | 28 | function setUp() public { 29 | vm.startPrank(user); 30 | 31 | token18 = new MockERC20("Token18", "TK18", 18); 32 | token6 = new MockERC20("Token6", "TK6", 6); 33 | 34 | vm.stopPrank(); 35 | 36 | // approve all tokens 37 | token18.approve(user, 1000 * 10 ** 18); 38 | token6.approve(user, 1000 * 10 ** 6); 39 | 40 | vm.startPrank(user); 41 | 42 | // transfer some tokens to test address 43 | token18.transfer(address(this), 1000 * 10 ** 18); 44 | token6.transfer(address(this), 1000 * 10 ** 6); 45 | 46 | token18.approve(address(this), 1000 * 10 ** 18); 47 | token6.approve(address(this), 1000 * 10 ** 6); 48 | } 49 | 50 | function test_NormalizeDecimalsUp() public { 51 | uint256 amount = 1 * 10 ** 6; 52 | uint256 normalized = TransferHelper.normalizeDecimals(amount, 6, 18); 53 | vm.assertEq(normalized, 1 * 10 ** 18); 54 | } 55 | 56 | function test_NormalizeDecimalsDown() public { 57 | uint256 amount = 1 * 10 ** 18; 58 | uint256 normalized = TransferHelper.normalizeDecimals(amount, 18, 6); 59 | vm.assertEq(normalized, 1 * 10 ** 6); 60 | } 61 | 62 | function test_NormalizeDecimalsNoChange() public { 63 | uint256 amount = 500 * 10 ** 18; 64 | uint256 normalized = TransferHelper.normalizeDecimals(amount, 18, 18); 65 | vm.assertEq(normalized, 500 * 10 ** 18); 66 | } 67 | 68 | function test_TokenNativeDecimalsToFixed() public { 69 | uint256 amount = 2 * 10 ** 6; 70 | uint256 fixedAmount = TransferHelper.tokenNativeDecimalsToFixed(amount, token6); 71 | vm.assertEq(fixedAmount, 2 * 10 ** 18); 72 | } 73 | 74 | function test_FixedToTokenNativeDecimals() public { 75 | uint256 amount = 3 * 10 ** 18; 76 | uint256 nativeAmount = TransferHelper.fixedToTokenNativeDecimals(amount, token6); 77 | vm.assertEq(nativeAmount, 3 * 10 ** 6); 78 | } 79 | 80 | function test_TransferNormalize() public { 81 | uint256 amount = 4 * 10 ** 18; 82 | token18.transfer(address(this), amount); 83 | TransferHelper.transferNormalize(token18, recipient, amount); 84 | vm.assertEq(token18.balanceOf(recipient), amount); 85 | } 86 | 87 | function test_TransferNormalizeWithDifferentDecimals() public { 88 | uint256 amount = 5 * 10 ** 18; 89 | TransferHelper.transferNormalize(token6, recipient, amount); 90 | vm.assertEq(token6.balanceOf(recipient), 5 * 10 ** 6); 91 | } 92 | 93 | function test_TransferFromNormalize() public { 94 | uint256 amount = 6 * 10 ** 18; 95 | 96 | vm.stopPrank(); 97 | 98 | uint256 balanceBefore = token18.balanceOf(address(this)); 99 | 100 | TransferHelper.transferFromNormalize(token18, user, amount); 101 | 102 | uint256 balanceAfter = token18.balanceOf(address(this)); 103 | 104 | vm.assertEq(balanceAfter, balanceBefore + amount); 105 | } 106 | 107 | function test_TransferFromNormalizeWithDifferentDecimals() public { 108 | uint256 amount = 7 * 10 ** 18; 109 | 110 | vm.stopPrank(); 111 | 112 | uint256 balanceBefore = token6.balanceOf(address(this)); 113 | 114 | TransferHelper.transferFromNormalize(token6, user, amount); 115 | 116 | uint256 balanceAfter = token6.balanceOf(address(this)); 117 | 118 | vm.assertEq(balanceAfter, balanceBefore + 7 * 10 ** 6); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /test/forge/unit/protectedUnitMath/LiquidtyMath.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.24; 2 | 3 | import "./../../../../contracts/libraries/ProtectedUnitMath.sol"; 4 | import "./../../../../contracts/interfaces/IErrors.sol"; 5 | import "forge-std/Test.sol"; 6 | import "forge-std/console.sol"; 7 | 8 | /// solhint-disable 9 | contract LiquidityMathTest is Test { 10 | function test_previewMint() external { 11 | uint256 reservePa = 10 ether; 12 | uint256 reserveDs = 5 ether; 13 | uint256 totalLiquidity = 10 ether; 14 | 15 | (uint256 amountDs, uint256 amountPa) = 16 | ProtectedUnitMath.previewMint(1 ether, reservePa, reserveDs, totalLiquidity); 17 | 18 | vm.assertEq(amountDs, 0.5 ether); 19 | vm.assertEq(amountPa, 1 ether); 20 | } 21 | 22 | function test_normalizeDecimals() external { 23 | uint256 amount = 1000 ether; 24 | uint8 decimals = 18; 25 | 26 | uint256 normalizedAmount = ProtectedUnitMath.normalizeDecimals(amount, 18, decimals); 27 | 28 | vm.assertEq(normalizedAmount, 1000 ether); 29 | 30 | decimals = 6; 31 | 32 | normalizedAmount = ProtectedUnitMath.normalizeDecimals(amount, 18, decimals); 33 | 34 | vm.assertEq(normalizedAmount, 1000 ether / 1e12); 35 | 36 | decimals = 24; 37 | 38 | normalizedAmount = ProtectedUnitMath.normalizeDecimals(amount, 18, decimals); 39 | 40 | vm.assertEq(normalizedAmount, 1000 ether * 1e6); 41 | } 42 | 43 | function test_addLiquidityFirst() external { 44 | uint256 reservePa = 0; 45 | uint256 reserveDs = 0; 46 | uint256 totalLiquidity = 0; 47 | 48 | uint256 amountPa = 1000 ether; 49 | uint256 amountDs = 1000 ether; 50 | 51 | uint256 liquidityMinted = ProtectedUnitMath.mint(reservePa, totalLiquidity, amountPa, amountDs); 52 | 53 | vm.assertEq(liquidityMinted, 1000 ether); 54 | } 55 | 56 | /// forge-config: default.allow_internal_expect_revert = true 57 | function testRevert_WhenaddLiquidityFirstNoProportional() external { 58 | uint256 reservePa = 0; 59 | uint256 reserveDs = 0; 60 | uint256 totalLiquidity = 0; 61 | 62 | uint256 amountPa = 1000 ether; 63 | uint256 amountDs = 900 ether; 64 | 65 | vm.expectRevert(IErrors.InvalidAmount.selector); 66 | ProtectedUnitMath.mint(reservePa, totalLiquidity, amountPa, amountDs); 67 | } 68 | 69 | function test_addLiquiditySubsequent() external { 70 | uint256 reservePa = 2000 ether; 71 | uint256 reserveDs = 1800 ether; 72 | uint256 totalLiquidity = 948.6832 ether; 73 | 74 | uint256 amountPa = 1000 ether; 75 | uint256 amountDs = 900 ether; 76 | 77 | uint256 liquidityMinted = ProtectedUnitMath.mint(reservePa, totalLiquidity, amountPa, amountDs); 78 | 79 | vm.assertApproxEqAbs(liquidityMinted, 474.3416491 ether, 0.0001 ether); 80 | } 81 | 82 | function test_removeLiquidity() external { 83 | uint256 reservePa = 2000 ether; 84 | uint256 reserveDs = 1800 ether; 85 | uint256 reserveRa = 100 ether; 86 | uint256 totalLiquidity = 948.6832 ether; 87 | 88 | uint256 liquidityAmount = 100 ether; 89 | (uint256 amountPa, uint256 amountDs, uint256 amountRa) = 90 | ProtectedUnitMath.withdraw(reservePa, reserveDs, reserveRa, totalLiquidity, liquidityAmount); 91 | 92 | vm.assertApproxEqAbs(amountPa, 210.818 ether, 0.001 ether); 93 | vm.assertApproxEqAbs(amountDs, 189.736 ether, 0.001 ether); 94 | vm.assertApproxEqAbs(amountRa, 10.54092662 ether, 0.001 ether); 95 | 96 | liquidityAmount = totalLiquidity; 97 | 98 | (amountPa, amountDs, amountRa) = 99 | ProtectedUnitMath.withdraw(reservePa, reserveDs, reserveRa, totalLiquidity, liquidityAmount); 100 | 101 | vm.assertEq(amountPa, 2000 ether); 102 | vm.assertEq(amountDs, 1800 ether); 103 | vm.assertEq(amountRa, 100 ether); 104 | } 105 | 106 | /// forge-config: default.allow_internal_expect_revert = true 107 | function testRevert_removeLiquidityInvalidLiquidity() external { 108 | uint256 reservePa = 2000 ether; 109 | uint256 reserveDs = 1800 ether; 110 | uint256 reserveRa = 100 ether; 111 | 112 | uint256 totalLiquidity = 948.6832 ether; 113 | 114 | uint256 liquidityAmount = 0; 115 | 116 | vm.expectRevert(); 117 | ProtectedUnitMath.withdraw(reservePa, reserveDs, reserveRa, totalLiquidity, liquidityAmount); 118 | } 119 | 120 | /// forge-config: default.allow_internal_expect_revert = true 121 | function testRevert_removeLiquidityNoLiquidity() external { 122 | uint256 reservePa = 2000 ether; 123 | uint256 reserveDs = 1800 ether; 124 | uint256 reserveRa = 100 ether; 125 | uint256 totalLiquidity = 0; 126 | 127 | uint256 liquidityAmount = 100 ether; 128 | 129 | vm.expectRevert(); 130 | ProtectedUnitMath.withdraw(reservePa, reserveDs, reserveRa, totalLiquidity, liquidityAmount); 131 | } 132 | 133 | function testFuzz_proportionalAmount(uint256 amountPa) external { 134 | amountPa = bound(amountPa, 1 ether, 100000 ether); 135 | 136 | uint256 reservePa = 1000 ether; 137 | uint256 reserveDs = 2000 ether; 138 | 139 | uint256 amountDs = ProtectedUnitMath.getProportionalAmount(amountPa, reservePa, reserveDs); 140 | 141 | vm.assertEq(amountDs, amountPa * 2); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /test/forge/unit/swapMath/arpMath.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.24; 2 | 3 | import "forge-std/Test.sol"; 4 | import {UD60x18, convert, add, mul, pow, sub, div, unwrap, ud} from "@prb/math/src/UD60x18.sol"; 5 | import {SwapperMathLibrary} from "./../../../../contracts/libraries/DsSwapperMathLib.sol"; 6 | import "forge-std/console.sol"; 7 | 8 | contract ArpMath is Test { 9 | function test_fixedCtPrice() external { 10 | uint256 arp = 0.05 ether; 11 | 12 | uint256 ratio = unwrap(SwapperMathLibrary.calcPtConstFixed(ud(arp))); 13 | 14 | vm.assertApproxEqAbs(ratio, 0.95 ether, 0.01 ether); 15 | } 16 | 17 | function test_effectiveDsPrice() external { 18 | UD60x18 dsAmount = convert(10e18); // 1 DS tokens 19 | UD60x18 raProvided = convert(5e18); // 5 RA tokens 20 | 21 | UD60x18 effectiveDsPrice = SwapperMathLibrary.calculateEffectiveDsPrice(dsAmount, raProvided); 22 | 23 | vm.assertEq(0.5 ether, unwrap(effectiveDsPrice)); 24 | } 25 | 26 | function test_calcHiyaAcc() external { 27 | // t = 1 28 | uint256 startTime = 1 days; 29 | uint256 maturityTime = 10 days; 30 | uint256 currentTime = 1 days; 31 | 32 | uint256 amount = 10 ether; 33 | uint256 raProvided = 5 ether; 34 | uint256 decayDiscountInDays = 5 ether; 35 | 36 | uint256 result = SwapperMathLibrary.calcHIYAaccumulated( 37 | startTime, maturityTime, currentTime, amount, raProvided, decayDiscountInDays 38 | ); 39 | 40 | vm.assertApproxEqAbs(result, 0.1 ether, 0.01 ether); 41 | } 42 | 43 | function test_calcVhiya() external { 44 | uint256 startTime = 1 days; 45 | uint256 maturityTime = 10 days; 46 | uint256 currentTime = 1 days; 47 | 48 | uint256 amount = 10 ether; 49 | uint256 decayDiscountInDays = 5 ether; 50 | 51 | uint256 result = SwapperMathLibrary.calcVHIYAaccumulated(startTime, currentTime, decayDiscountInDays, amount); 52 | 53 | vm.assertEq(result, 10 ether); 54 | } 55 | 56 | function test_calcHiya() external { 57 | // t = 1 58 | uint256 startTime = 1 days; 59 | uint256 maturityTime = 10 days; 60 | uint256 currentTime = 1 days; 61 | 62 | uint256 amount = 10 ether; 63 | uint256 raProvided = 5 ether; 64 | uint256 decayDiscountInDays = 5 ether; 65 | 66 | uint256 hiyaAcc = SwapperMathLibrary.calcHIYAaccumulated( 67 | startTime, maturityTime, currentTime, amount, raProvided, decayDiscountInDays 68 | ); 69 | uint256 vhiyaAcc = SwapperMathLibrary.calcVHIYAaccumulated(startTime, currentTime, decayDiscountInDays, amount); 70 | 71 | uint256 result = SwapperMathLibrary.calculateHIYA(hiyaAcc, vhiyaAcc); 72 | 73 | vm.assertApproxEqAbs(result, 0.01 ether, 0.001 ether); 74 | } 75 | 76 | function test_calcPt() external { 77 | UD60x18 dsPrice = ud(0.4 ether); 78 | UD60x18 pt = SwapperMathLibrary.calcPt(dsPrice); 79 | 80 | vm.assertEq(unwrap(pt), 0.6 ether); 81 | } 82 | 83 | function test_calcRt() external { 84 | UD60x18 pt = ud(0.6 ether); 85 | UD60x18 rt = SwapperMathLibrary.calcRt(pt, convert(1)); 86 | vm.assertApproxEqAbs(0.66 ether, unwrap(rt), 0.01 ether); 87 | } 88 | 89 | function calcSpotArp() external { 90 | UD60x18 result = SwapperMathLibrary.calcSpotArp(convert(1), convert(0.4 ether)); 91 | vm.assertApproxEqAbs(0.66 ether, unwrap(result), 0.01 ether); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /test/forge/unit/swapMath/buyMath.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.24; 2 | 3 | import "forge-std/Test.sol"; 4 | import {SD59x18, convert, sd, add, mul, pow, sub, div, abs, unwrap} from "@prb/math/src/SD59x18.sol"; 5 | import "./../../../../contracts/libraries/DsSwapperMathLib.sol"; 6 | import {SwapMath} from "Cork-Hook/lib/SwapMath.sol"; 7 | import "forge-std/console.sol"; 8 | import "./../../../../contracts/interfaces/IErrors.sol"; 9 | 10 | contract BuyMathTest is Test { 11 | int256 internal constant START = 0 days; 12 | int256 internal constant END = 100 days; 13 | 14 | function test_t() external { 15 | SD59x18 current = convert(10 days); 16 | SD59x18 t = BuyMathBisectionSolver.computeT(convert(START), convert(END), current); 17 | 18 | int256 tUnwrapped = unwrap(t); 19 | vm.assertEq(tUnwrapped, 0.9e18); 20 | } 21 | 22 | function test_1minT() external { 23 | SD59x18 current = convert(10 days); 24 | SD59x18 t = BuyMathBisectionSolver.computeOneMinusT(convert(START), convert(END), current); 25 | 26 | int256 tUnwrapped = unwrap(t); 27 | vm.assertEq(tUnwrapped, 0.1e18); 28 | } 29 | 30 | function test_buyMath() external { 31 | vm.pauseGasMetering(); 32 | SD59x18 x = convert(1000 ether); 33 | SD59x18 y = convert(1050 ether); 34 | SD59x18 e = convert(0.5 ether); 35 | vm.resumeGasMetering(); 36 | 37 | SD59x18 _1MinusT = sd(0.01 ether); 38 | 39 | uint256 result = uint256(convert(BuyMathBisectionSolver.findRoot(x, y, e, _1MinusT, sd(1e9), 256))); 40 | 41 | vm.pauseGasMetering(); 42 | vm.assertApproxEqAbs(result, 9.054 ether, 0.001 ether); 43 | } 44 | 45 | function test_buyMathWithChecks() external { 46 | uint256 x = 1000 ether; 47 | uint256 y = 1050 ether; 48 | uint256 e = 0.5 ether; 49 | uint256 start = 0 days; 50 | uint256 end = 100 days; 51 | uint256 current = 1 days; 52 | 53 | uint256 result = SwapperMathLibrary.getAmountOutBuyDs(x, y, e, start, end, current, 1e9, 256); 54 | 55 | vm.assertApproxEqAbs(result, 9.054 ether, 0.001 ether); 56 | } 57 | 58 | function test_RevertbuyMathWhenExpiredOrVeryCloseToExpiry() external { 59 | uint256 x = 1000 ether; 60 | uint256 y = 1050 ether; 61 | uint256 e = 0.5 ether; 62 | uint256 start = 1 days; 63 | uint256 end = 365 days; 64 | uint256 current = 365.1 days; 65 | 66 | vm.expectRevert(IErrors.Expired.selector); 67 | SwapperMathLibrary.getAmountOutBuyDs(x, y, e, start, end, current, 1e9, 256); 68 | 69 | current = 364.99 days; 70 | // simulate a massive imbalance 71 | y = 40 ether; 72 | vm.expectRevert(IErrors.InvalidPoolStateOrNearExpired.selector); 73 | SwapperMathLibrary.getAmountOutBuyDs(x, y, e, start, end, current, 1e9, 256); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /test/forge/unit/tokenNames/TokenNames.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.24; 2 | 3 | import "./../../Helper.sol"; 4 | import "@openzeppelin/contracts/interfaces/IERC20Metadata.sol"; 5 | 6 | contract TokenName is Helper { 7 | function setUp() external { 8 | vm.startPrank(DEFAULT_ADDRESS); 9 | // saturday, June 3, 2000 1:07:47 PM 10 | // 06/03/2000 @ 1:07:47pm 11 | vm.warp(960037567); 12 | deployModuleCore(); 13 | initializeAndIssueNewDs(100); 14 | } 15 | 16 | function test_tokenNames() external { 17 | (address ct, address ds) = moduleCore.swapAsset(defaultCurrencyId, 1); 18 | address lv = moduleCore.lvAsset(defaultCurrencyId); 19 | 20 | IERC20Metadata ctToken = IERC20Metadata(ct); 21 | IERC20Metadata dsToken = IERC20Metadata(ds); 22 | IERC20Metadata lvToken = IERC20Metadata(lv); 23 | 24 | vm.assertEq(ctToken.symbol(), "DWETH6CT-1"); 25 | vm.assertEq(dsToken.symbol(), "DWETH6DS-1"); 26 | vm.assertEq(lvToken.symbol(), "DWETH!LV-1"); 27 | 28 | initializeAndIssueNewDs(1000); 29 | 30 | (ct, ds) = moduleCore.swapAsset(defaultCurrencyId, 1); 31 | lv = moduleCore.lvAsset(defaultCurrencyId); 32 | 33 | ctToken = IERC20Metadata(ct); 34 | dsToken = IERC20Metadata(ds); 35 | lvToken = IERC20Metadata(lv); 36 | 37 | vm.assertEq(ctToken.symbol(), "DWETH6CT-2"); 38 | vm.assertEq(dsToken.symbol(), "DWETH6DS-2"); 39 | vm.assertEq(lvToken.symbol(), "DWETH!LV-2"); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /test/forge/unit/vaultMath/MathHelper.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | import "./../../../../contracts/libraries/MathHelper.sol"; 4 | import "./../../Helper.sol"; 5 | 6 | contract MathHelperTest is Helper { 7 | function testFuzz_shouldCalculateProvideLiquidityAmountCorrectly11(uint256 amountRa) external { 8 | // no more than 10 thousand TRILLION * 10^18 9 | vm.assume(amountRa < 10000000000000000 ether); 10 | 11 | uint256 priceRatio = 1 ether; 12 | uint256 exchangeRate = 1 ether; 13 | 14 | (uint256 ra, uint256 ct) = MathHelper.calculateProvideLiquidityAmountBasedOnCtPrice(amountRa, priceRatio); 15 | 16 | vm.assertApproxEqAbs(ra, amountRa / 2, 1); 17 | vm.assertApproxEqAbs(ct, amountRa / 2, 1); 18 | } 19 | 20 | function test_DsRedeemAmount() external { 21 | uint256 rates = 1.1 ether; 22 | uint256 pa = 5 ether; 23 | 24 | uint256 amount = MathHelper.calculateEqualSwapAmount(pa, rates); 25 | 26 | vm.assertEq(amount, 5.5 ether); 27 | } 28 | 29 | function testFuzz_DsRedeemAmount(uint256 pa, uint256 rates) external { 30 | pa = bound(pa, 0.1 ether, 10 ether); 31 | rates = bound(rates, 1 ether, 1.5 ether); 32 | 33 | uint256 amount = MathHelper.calculateEqualSwapAmount(pa, rates); 34 | 35 | vm.assertEq(amount, pa * rates / 1e18); 36 | } 37 | 38 | function test_initialCtRatioBasedOnArp() external { 39 | uint256 arp = 5 ether; 40 | 41 | uint256 ratio = MathHelper.calculateInitialCtRatio(arp); 42 | 43 | vm.assertApproxEqAbs(ratio, 0.95 ether, 0.01 ether); 44 | } 45 | 46 | function test_calculateRepurchaseFee() external { 47 | uint256 start = 0 days; 48 | uint256 end = 1 days; 49 | uint256 current = 0 days; 50 | 51 | // 10 percent 52 | uint256 feePercentage = 10 ether; 53 | uint256 amount = 1 ether; 54 | 55 | (uint256 result, uint256 actualPercentage) = 56 | MathHelper.calculateRepurchaseFee(start, end, current, amount, feePercentage); 57 | 58 | vm.assertApproxEqAbs(actualPercentage, 10 ether, 0.01 ether); 59 | vm.assertApproxEqAbs(result, 0.1 ether, 0.01 ether); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /test/forge/unit/vaultMath/navMath.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.24; 2 | 3 | import "forge-std/Test.sol"; 4 | import "./../../../../contracts/libraries/MathHelper.sol"; 5 | import "./../../Helper.sol"; 6 | import "forge-std/console.sol"; 7 | import {UD60x18, convert, add, mul, pow, sub, div, unwrap, ud} from "@prb/math/src/UD60x18.sol"; 8 | 9 | contract NavMathTest is Test { 10 | UD60x18 internal raReserve = convert(1000 ether); 11 | UD60x18 internal ctReserve = convert(1050 ether); 12 | 13 | function test_quote() external { 14 | uint256 raQuote = unwrap(MathHelper.calculatePriceQuote(ctReserve, raReserve, ud(0.9 ether))); 15 | 16 | vm.assertApproxEqAbs(raQuote, 1.044 ether, 0.001 ether); 17 | 18 | uint256 ctQuote = unwrap(MathHelper.calculatePriceQuote(raReserve, ctReserve, ud(0.9 ether))); 19 | 20 | vm.assertApproxEqAbs(ctQuote, 0.957 ether, 0.001 ether); 21 | } 22 | 23 | function test_calculateNav() external { 24 | UD60x18 marketValue = ud(2 ether); 25 | UD60x18 qty = ud(3 ether); 26 | 27 | uint256 nav = unwrap(MathHelper.calculateNav(marketValue, qty)); 28 | 29 | vm.assertEq(nav, 6 ether); 30 | } 31 | 32 | function test_calculateInternalPrices() external { 33 | MathHelper.NavParams memory params = MathHelper.NavParams({ 34 | reserveRa: 1000 ether, 35 | oneMinusT: 0.1 ether, 36 | reserveCt: 1050 ether, 37 | lpSupply: 1024 ether, 38 | lvSupply: 2050 ether, 39 | vaultCt: 1000 ether, 40 | vaultDs: 2050 ether, 41 | vaultLp: 1024 ether, 42 | vaultIdleRa: 15 ether 43 | }); 44 | 45 | MathHelper.InternalPrices memory prices = MathHelper.calculateInternalPrice(params); 46 | vm.assertApproxEqAbs(unwrap(prices.ctPrice), 0.957 ether, 0.001 ether); 47 | vm.assertApproxEqAbs(unwrap(prices.raPrice), 1 ether, 0.001 ether); 48 | vm.assertApproxEqAbs(unwrap(prices.dsPrice), 0.042 ether, 0.001 ether); 49 | } 50 | 51 | function test_calculateNavCombined() external { 52 | MathHelper.NavParams memory params = MathHelper.NavParams({ 53 | reserveRa: 1000 ether, 54 | oneMinusT: 0.1 ether, 55 | reserveCt: 1050 ether, 56 | lpSupply: 1024 ether, 57 | lvSupply: 2050 ether, 58 | vaultCt: 1000 ether, 59 | vaultDs: 2050 ether, 60 | vaultLp: 1024 ether, 61 | vaultIdleRa: 15 ether 62 | }); 63 | 64 | (UD60x18 navLp, UD60x18 navCt, UD60x18 navDs, UD60x18 navIdleRa) = MathHelper.calculateNavCombined(params); 65 | vm.assertApproxEqAbs(unwrap(navLp), 2004.8909 ether, 0.0001 ether); 66 | vm.assertApproxEqAbs(unwrap(navCt), 957.03 ether, 0.01 ether); 67 | vm.assertApproxEqAbs(unwrap(navDs), 88.07 ether, 0.01 ether); 68 | vm.assertApproxEqAbs(unwrap(navIdleRa), 15 ether, 0.0001 ether); 69 | } 70 | 71 | function test_calculateLvMinted() external { 72 | MathHelper.NavParams memory params = MathHelper.NavParams({ 73 | reserveRa: 1000 ether, 74 | oneMinusT: 0.1 ether, 75 | reserveCt: 1050 ether, 76 | lpSupply: 1024 ether, 77 | lvSupply: 2050 ether, 78 | vaultCt: 1000 ether, 79 | vaultDs: 2050 ether, 80 | vaultLp: 1024 ether, 81 | vaultIdleRa: 15 ether 82 | }); 83 | uint256 nav = MathHelper.calculateNav(params); 84 | uint256 minted = MathHelper.calculateDepositLv(nav, 1 ether, params.lvSupply); 85 | vm.assertApproxEqAbs(minted, 0.668 ether, 0.01 ether); 86 | } 87 | 88 | function test_Claim() external { 89 | MathHelper.RedeemParams memory params = MathHelper.RedeemParams({ 90 | amountLvClaimed: 10 ether, 91 | totalLvIssued: 100 ether, 92 | totalVaultLp: 450 ether, 93 | totalVaultCt: 300 ether, 94 | totalVaultDs: 700 ether, 95 | totalVaultPA: 24 ether, 96 | totalVaultIdleRa: 15 ether 97 | }); 98 | 99 | MathHelper.RedeemResult memory result = MathHelper.calculateRedeemLv(params); 100 | 101 | vm.assertEq(result.ctReceived, 30 ether); 102 | vm.assertEq(result.dsReceived, 70 ether); 103 | vm.assertEq(result.lpLiquidated, 45 ether); 104 | vm.assertEq(result.idleRaReceived, 1.5 ether); 105 | vm.assertEq(result.paReceived, 2.4 ether); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /test/lib/SwapMath.ts: -------------------------------------------------------------------------------- 1 | import { 2 | time, 3 | loadFixture, 4 | } from "@nomicfoundation/hardhat-toolbox-viem/network-helpers"; 5 | import { expect } from "chai"; 6 | import hre from "hardhat"; 7 | import { formatEther, parseEther } from "viem"; 8 | import * as helper from "../helper/TestHelper"; 9 | 10 | const DAY_IN_SECS = 86400n; 11 | 12 | describe("SwapMath", function () { 13 | async function deploySwapMath() { 14 | return await hre.viem.deployContract("SwapperMathLibrary"); 15 | } 16 | let swapMath: Awaited>; 17 | 18 | before(async function () { 19 | swapMath = await loadFixture(deploySwapMath); 20 | }); 21 | 22 | it("should deploy the contract", async function () { 23 | expect(swapMath.address).to.be.properAddress; 24 | }); 25 | 26 | it("should calculate DS received correctly", async function () { 27 | // y 28 | const ctReserve = parseEther("100000000"); 29 | //x 30 | const raReserve = parseEther("90000000"); 31 | 32 | //e 33 | const raProvided = parseEther("1"); 34 | 35 | const [raBorrowed, dsReturned] = await swapMath.read.getAmountOutBuyDs([ 36 | raReserve, 37 | ctReserve, 38 | raProvided, 39 | ]); 40 | 41 | expect(raBorrowed).to.be.closeTo( 42 | helper.toEthersBigNumer("9"), 43 | helper.toEthersBigNumer("0.1") 44 | ); 45 | 46 | expect(dsReturned).to.be.closeTo( 47 | helper.toEthersBigNumer("9.99"), 48 | helper.toEthersBigNumer("0.01") 49 | ); 50 | }); 51 | 52 | it("should calculate discount", async function () { 53 | const issuanceTimestamp = DAY_IN_SECS * 1n; 54 | // 28 days after issuance 55 | const currenTime = DAY_IN_SECS * 29n; 56 | // 2 % per day 57 | const decayDiscountInDays = parseEther("2"); 58 | 59 | const result = await swapMath.read.calculateDecayDiscount([ 60 | decayDiscountInDays, 61 | issuanceTimestamp, 62 | currenTime, 63 | ]); 64 | 65 | expect(result).to.be.closeTo( 66 | helper.toEthersBigNumer("44"), 67 | // precision up to 11 decimals 68 | helper.toEthersBigNumer("0.000000000001") 69 | ); 70 | }); 71 | 72 | it("should calculate cumulated HPA", async function () { 73 | const issuanceTimestamp = DAY_IN_SECS * 1n; 74 | // 28 days after issuance 75 | const currenTime = DAY_IN_SECS * 29n; 76 | // 2 % per day 77 | const decayDiscountInDays = parseEther("2"); 78 | 79 | const result = await swapMath.read.calculateHPAcumulated([ 80 | parseEther("0.1"), 81 | parseEther("100"), 82 | decayDiscountInDays, 83 | issuanceTimestamp, 84 | currenTime, 85 | ]); 86 | 87 | expect(result).to.be.closeTo( 88 | helper.toEthersBigNumer("4.4"), 89 | // precision up to 11 decimals 90 | helper.toEthersBigNumer("0.000000000001") 91 | ); 92 | }); 93 | 94 | it("should calculate cumulated VHPA", async function () { 95 | const issuanceTimestamp = DAY_IN_SECS * 1n; 96 | // 28 days after issuance 97 | const currenTime = DAY_IN_SECS * 29n; 98 | // 2 % per day 99 | const decayDiscountInDays = parseEther("2"); 100 | 101 | const result = await swapMath.read.calculateVHPAcumulated([ 102 | parseEther("100"), 103 | decayDiscountInDays, 104 | issuanceTimestamp, 105 | currenTime, 106 | ]); 107 | 108 | expect(result).to.be.closeTo( 109 | helper.toEthersBigNumer("44"), 110 | // precision up to 11 decimals 111 | helper.toEthersBigNumer("0.000000000001") 112 | ); 113 | }); 114 | 115 | it("should calculate HPA", async function () { 116 | let result = await swapMath.read.calculateHPA([ 117 | parseEther("4.4"), 118 | parseEther("44"), 119 | ]); 120 | 121 | expect(result).to.be.closeTo( 122 | helper.toEthersBigNumer("0.1"), 123 | // precision up to 11 decimals 124 | helper.toEthersBigNumer("0.000000000001") 125 | ); 126 | }); 127 | 128 | it("should calculate effective DS price", async function () { 129 | const raProvided = parseEther("0.1"); 130 | const dsReturned = parseEther("10000"); 131 | const result = await swapMath.read.calculateEffectiveDsPrice([ 132 | dsReturned, 133 | raProvided, 134 | ]); 135 | 136 | expect(result).to.be.equal(parseEther("0.00001")); 137 | }); 138 | }); 139 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "commonjs", 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "strict": true, 8 | "skipLibCheck": true, 9 | "resolveJsonModule": true 10 | } 11 | } 12 | --------------------------------------------------------------------------------