├── .dockerignore ├── .env.example ├── .gitignore ├── .mocharc.json ├── .prettierignore ├── .prettierrc.yml ├── .solcover.js ├── .yarnrc.yml ├── Dockerfile ├── License ├── README.md ├── abis ├── multiaccount.json ├── partyB.json └── symmio.json ├── contracts ├── CallProxy.sol ├── Diamond.sol ├── FakeStableCoin.sol ├── SymmioFeeDistributor.sol ├── SymmioTimelockController.sol ├── dev │ └── multicall.sol ├── facets │ ├── Account │ │ ├── AccountFacet.sol │ │ ├── AccountFacetImpl.sol │ │ ├── IAccountEvents.sol │ │ └── IAccountFacet.sol │ ├── Bridge │ │ ├── BridgeFacet.sol │ │ ├── BridgeFacetImpl.sol │ │ ├── IBridgeEvents.sol │ │ └── IBridgeFacet.sol │ ├── Control │ │ ├── ControlFacet.sol │ │ ├── IControlEvents.sol │ │ └── IControlFacet.sol │ ├── DiamondCut │ │ ├── DiamondCutFacet.sol │ │ └── IDiamondCut.sol │ ├── DiamondLoup │ │ ├── DiamondLoupFacet.sol │ │ └── IDiamondLoupe.sol │ ├── ForceActions │ │ ├── ForceActionsFacet.sol │ │ ├── ForceActionsFacetEvents.sol │ │ ├── ForceActionsFacetImpl.sol │ │ └── IForceActionsFacet.sol │ ├── FundingRate │ │ ├── FundingRateFacet.sol │ │ ├── FundingRateFacetImpl.sol │ │ ├── IFundingRateEvents.sol │ │ └── IFundingRateFacet.sol │ ├── PartyA │ │ ├── IPartyAEvents.sol │ │ ├── IPartyAFacet.sol │ │ ├── PartyAFacet.sol │ │ └── PartyAFacetImpl.sol │ ├── PartyBGroupActions │ │ ├── IPartyBGroupActionsFacet.sol │ │ └── PartyBGroupActionsFacet.sol │ ├── PartyBPositionActions │ │ ├── IPartyBPositionActionsEvents.sol │ │ ├── IPartyBPositionActionsFacet.sol │ │ ├── PartyBPositionActionsFacet.sol │ │ └── PartyBPositionActionsFacetImpl.sol │ ├── PartyBQuoteActions │ │ ├── IPartyBQuoteActionsEvents.sol │ │ ├── IPartyBQuoteActionsFacet.sol │ │ ├── PartyBQuoteActionsFacet.sol │ │ └── PartyBQuoteActionsFacetImpl.sol │ ├── Settlement │ │ ├── ISettlementFacet.sol │ │ ├── SettlementFacet.sol │ │ ├── SettlementFacetEvents.sol │ │ └── SettlementFacetImpl.sol │ ├── ViewFacet │ │ ├── IViewFacet.sol │ │ └── ViewFacet.sol │ └── liquidation │ │ ├── DeferredLiquidationFacetImpl.sol │ │ ├── ILiquidationEvents.sol │ │ ├── ILiquidationFacet.sol │ │ ├── LiquidationFacet.sol │ │ └── LiquidationFacetImpl.sol ├── helpers │ ├── NextQuoteIDVerifier.sol │ └── SymmioInitHelper.sol ├── interfaces │ ├── IERC165.sol │ ├── IMultiAccount.sol │ ├── IPartiesEvents.sol │ ├── ISymmio.sol │ └── ISymmioPartyA.sol ├── libraries │ ├── DevLogging.sol │ ├── LibAccessibility.sol │ ├── LibAccount.sol │ ├── LibDiamond.sol │ ├── LibLiquidation.sol │ ├── LibLockedValues.sol │ ├── LibMuonV04ClientBase.sol │ ├── LibPartyBPositionsActions.sol │ ├── LibPartyBQuoteActions.sol │ ├── LibQuote.sol │ ├── LibSettlement.sol │ ├── LibSolvency.sol │ ├── LibUtils.sol │ ├── SharedEvents.sol │ └── muon │ │ ├── LibMuon.sol │ │ ├── LibMuonAccount.sol │ │ ├── LibMuonForceActions.sol │ │ ├── LibMuonFundingRate.sol │ │ ├── LibMuonLiquidation.sol │ │ ├── LibMuonPartyA.sol │ │ ├── LibMuonPartyB.sol │ │ └── LibMuonSettlement.sol ├── multiAccount │ ├── MultiAccount.sol │ ├── SymmioPartyA.sol │ └── SymmioPartyB.sol ├── storages │ ├── AccountStorage.sol │ ├── BridgeStorage.sol │ ├── GlobalAppStorage.sol │ ├── MAStorage.sol │ ├── MuonStorage.sol │ ├── QuoteStorage.sol │ └── SymbolStorage.sol ├── test │ ├── MockSymmio.sol │ └── MockToken.sol ├── upgradeInitializers │ └── DiamondInit.sol └── utils │ ├── Accessibility.sol │ ├── Ownable.sol │ └── Pausable.sol ├── deploy-local.sh ├── docker ├── compile.sh └── deploy.sh ├── funding.json ├── fuzz-tests ├── hardhat.config.ts ├── images ├── liq_example1.png └── liq_example2.png ├── muon ├── README.md └── symmio.js ├── package.json ├── scripts ├── Initialize.ts ├── callAMethod.ts ├── config │ └── setup.example.json ├── deploy.ts ├── deployAll.ts ├── deployCallProxy.ts ├── deployFeeDistributor.ts ├── deployMultiAccount.ts ├── deployPartyB.ts ├── deployTimelock.ts ├── diamondUpgrade.ts ├── fuzz │ └── WakeUpUser.ts ├── gasEstimate.ts ├── prepareDiamondUpgrade.ts ├── symmSetup.ts ├── updateFacet.ts ├── utils │ ├── diamondUtils.ts │ └── file.ts └── verifyAll.ts ├── static-tests ├── tasks ├── deploy │ ├── callProxy.ts │ ├── constants.ts │ ├── diamond.ts │ ├── feeDistributor.ts │ ├── index.ts │ ├── multiaccount.ts │ ├── multicall.ts │ ├── nextQuoteIdVerifier.ts │ ├── partyB.ts │ ├── stablecoin.ts │ └── verify.ts └── utils │ ├── diamondCut.ts │ ├── fs.ts │ └── gas.ts ├── test ├── AccountFacet.behavior.ts ├── BridgeFacet.behavior.ts ├── CancelQuote.behavior.ts ├── ClosePosition.behavior.ts ├── ControlFacet.behavior.ts ├── Diamond.behavior.ts ├── EmergencyClosePosition.behavior.ts ├── FeeDistributor.behavior.ts ├── ForceClosePosition.behavior.ts ├── FundingRate.behavior.ts ├── FuzzTest.behavior.ts ├── Initialize.fixture.ts ├── LiquidationFacet.behavior.ts ├── LockQuote.behavior.ts ├── Main.ts ├── MultiAccount.behavior.ts ├── OpenPosition.behavior.ts ├── PreUpgrade.behavior.ts ├── SendQuote.behavior.ts ├── SettleAndForceClosePosition.behavior.ts ├── Settlement.behavior.ts ├── SpecificScenario.behavior.ts ├── models │ ├── Actions.ts │ ├── BalanceInfo.ts │ ├── Enums.ts │ ├── EventListener.ts │ ├── Hedger.ts │ ├── HedgerController.ts │ ├── ManagedError.ts │ ├── RunContext.ts │ ├── SymbolManager.ts │ ├── TestManager.ts │ ├── User.ts │ ├── UserController.ts │ ├── quoteCheckpoint.ts │ ├── requestModels │ │ ├── CloseRequest.ts │ │ ├── EmergencyCloseRequest.ts │ │ ├── FillCloseRequest.ts │ │ ├── OpenRequest.ts │ │ └── QuoteRequest.ts │ └── validators │ │ ├── AcceptCancelCloseRequestValidator.ts │ │ ├── AcceptCancelRequestValidator.ts │ │ ├── CancelCloseRequestValidator.ts │ │ ├── CancelQuoteValidator.ts │ │ ├── CloseRequestValidator.ts │ │ ├── EmergencyCloseRequestValidator.ts │ │ ├── FillCloseRequestValidator.ts │ │ ├── ForceClosePositionValidator.ts │ │ ├── LockQuoteValidator.ts │ │ ├── OpenPositionValidator.ts │ │ ├── SendQuoteValidator.ts │ │ ├── TransactionValidator.ts │ │ ├── TransferToBridgeValidator.ts │ │ ├── UnlockQuoteValidator.ts │ │ └── WithdrawLockedTransactionValidator.ts └── utils │ ├── Common.ts │ ├── LoggerUtils.ts │ ├── Pauser.ts │ ├── PriceUtils.ts │ ├── RandomUtils.ts │ ├── SafeMath.ts │ ├── SignatureUtils.ts │ └── TxUtils.ts ├── tsconfig.json └── utils ├── clean.sh ├── deploy-local.sh ├── fuzz-tests ├── get_selectors.py ├── get_symbol_details_config_setup.py ├── make_abi.py ├── remove_duplicate_events.py ├── runTests.sh ├── runTestsWithLocalNode.sh ├── static-tests └── update_sig_checks.py /.dockerignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /cache/ 3 | /artifacts/ 4 | /src/ 5 | /package-lock.json 6 | .cache 7 | .local 8 | .npm 9 | /.env 10 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | MANTLE_API_KEY="" 2 | MANTLE2_API_KEY="" 3 | BNB_API_KEY="" 4 | BLAST_API_KEY="" 5 | BASE_API_KEY="" 6 | POLYGON_API_KEY="" 7 | ARBITRUM_API_KEY="" 8 | IOTA_API_KEY="" 9 | MODE_API_KEY="" 10 | BERA_API_KEY="" 11 | 12 | PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" 13 | PRIVATE_KEYS_STR="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80,0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d,0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a,0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6,0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a,0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba,0x92db14e403b83dfe3df233f83dfa3a0d7096f21ca9b0d6d6b8d88b2b4ec1564e,0x4bbbf85ce3377467afe5d46f804f221813b2bb87f24d81f60f1fcdbf7cbf4356,0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97,0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6,0xf214f2b2cd398c806f84e317254e0f0b801d0643303237d97a22a48e01628897,0x701b615bbdfb9de65240bc28bd21bbc0d996645a3dd57e7b12bc2bdf6f192c82,0xa267530f49f8280200edf313ee7af6b827f2a8bce2897751d06a843f644967b1,0x47c99abed3324a2707c28affff1267e45918ec8c3f20b8aa892e8b065d2942dd,0xc526ee95bf44d8fc405a158bb884d9d1238d99f0612e9f33d006bb0789009aaa,0x8166f546bab6da521a8369cab06c5d2b9e46670292d85c875ee9ec20e84ffb61,0xea6c44ac03bff858b476bba40716402b03e41b8e97e276d1baec7c37d42484a0,0x689af8efa8c651a91ad287602527f3af2fe9f6501a7ac4b061667b5a93e037fd,0xde9be858da4a475276426320d5e9262ecfc3ba460bfac56360bfa6c4c28b4ee0,0xdf57089febbacf7ba0bc227dafbffa9fc08a93fdc68e1e42411a14efcf23656e" 14 | ADMIN_PUBLIC_KEY="0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" 15 | HARDHAT_DOCKER_URL="http://eth-node:8545" 16 | 17 | TEST_USER_BALANCE=5000 18 | TEST_USER_MAX_QUOTE=100 19 | HEDGER_WEB_SERVICE="http://127.0.0.1:8090" 20 | TEST_USER_TIMEOUT=120000 21 | LOG_LEVEL=debug 22 | DEPLOY_MULTICALL=false 23 | VALIDATION_PROBABILITY=0.2 24 | DEPLOY_MULTICALL=true 25 | 26 | TEST_MODE={static,pre-upgrade,fuzz} 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /package-lock.json 3 | .env 4 | coverage 5 | coverage.json 6 | typechain 7 | typechain-types 8 | 9 | #Hardhat files 10 | /cache/ 11 | /artifacts/ 12 | /src/ 13 | .cache 14 | .local 15 | .npm 16 | 17 | node_modules 18 | .env 19 | coverage 20 | coverage.json 21 | typechain 22 | typechain-types 23 | 24 | yarn.lock 25 | .idea 26 | addresses.json 27 | /abi.json 28 | .vscode 29 | tasks/data 30 | output.log 31 | serverLog.log 32 | detailedDebug.log 33 | pnpm-lock.yaml 34 | .openzeppelin 35 | 36 | docs 37 | .yarn/ 38 | .venv -------------------------------------------------------------------------------- /.mocharc.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": "hardhat/register", 3 | "timeout": 40000, 4 | "_": [ 5 | "test/**/*.ts" 6 | ] 7 | } -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # directories 2 | .yarn/ 3 | **/.coverage_artifacts 4 | **/.coverage_cache 5 | **/.coverage_contracts 6 | **/artifacts 7 | **/build 8 | **/cache 9 | **/coverage 10 | **/dist 11 | **/node_modules 12 | **/types 13 | .typechain 14 | 15 | # files 16 | **/.env 17 | *.log 18 | .pnp.* 19 | coverage.json 20 | npm-debug.log* 21 | yarn-debug.log* 22 | yarn-error.log* -------------------------------------------------------------------------------- /.prettierrc.yml: -------------------------------------------------------------------------------- 1 | arrowParens: avoid 2 | bracketSpacing: true 3 | endOfLine: auto 4 | printWidth: 150 5 | singleQuote: false 6 | tabWidth: 4 7 | trailingComma: all 8 | useTabs: true 9 | 10 | overrides: 11 | - files: "*.sol" 12 | options: 13 | compiler: "0.8.17" 14 | - files: "*.ts" 15 | options: 16 | tabWidth: 2 17 | semi: false 18 | importOrder: ["", "^[./]"] 19 | importOrderParserPlugins: ["typescript"] 20 | importOrderSeparation: true 21 | importOrderSortSpecifiers: false 22 | -------------------------------------------------------------------------------- /.solcover.js: -------------------------------------------------------------------------------- 1 | module.exports= { 2 | skipFiles:['dev'] 3 | } -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Project: symmio 2 | # Description: - 3 | 4 | FROM node:lts 5 | 6 | ###################################################################### 7 | # LABELS 8 | ###################################################################### 9 | ARG COMMIT_ID 10 | ARG COMMIT_TIMESTAMP 11 | ARG COMMIT_AUTHOR 12 | ARG BUILD_APPLICATION 13 | ARG BUILD_DATE 14 | ARG VERIFY_MUON_SIG 15 | 16 | LABEL org.vcs.CommitId=${COMMIT_ID} 17 | LABEL org.vcs.CommitTimestamp=${COMMIT_TIMESTAMP} 18 | LABEL org.vcs.CommitAuthor=${COMMIT_AUTHOR} 19 | LABEL org.build.Application=${BUILD_APPLICATION} 20 | LABEL org.build.Date=${BUILD_DATE} 21 | 22 | ###################################################################### 23 | # BUILD STAGE 24 | ###################################################################### 25 | RUN npm config set fetch-retries 10 26 | RUN npm config set fetch-retry-mintimeout 20000 27 | RUN npm install -g npm 28 | RUN mkdir /app 29 | 30 | COPY package.json /app/ 31 | WORKDIR /app 32 | 33 | RUN npm install --ignore-scripts 34 | 35 | RUN mkdir -p /app/symmio 36 | 37 | COPY . /app/symmio 38 | WORKDIR /app/symmio 39 | RUN cp .env.example .env 40 | RUN ln -s /app/node_modules . 41 | RUN npm run postinstall 42 | RUN if [ "$VERIFY_MUON_SIG" != "true" ] ; then python3 utils/update_sig_checks.py 1 ; fi 43 | RUN ./docker/compile.sh 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SYMMIO: Decentralized Derivatives Protocol 2 | 3 | SYMMIO is a trustless hybrid clearing house (combining on-chain and off-chain components) acting as a communication, 4 | settlement, and clearing layer for permissionless derivatives. At its core, SYMMIO is an intent-centric, 5 | meta-derivatives engine, with its first use case being a new type of hyper-efficient perpetuals trading technology. 6 | 7 | ## Code Architecture 8 | 9 | This project utilizes the Diamond Proxy pattern ([EIP-2535](https://eips.ethereum.org/EIPS/eip-2535)) for upgradability 10 | and modularity. Currently, we have 13 facets: 11 | 12 | 1. **AccountFacet** 13 | 2. **ControlFacet** 14 | 3. **DiamondLoupeFacet** 15 | 4. **LiquidationFacet** 16 | 5. **PartyAFacet** 17 | 6. **BridgeFacet** 18 | 7. **ViewFacet** 19 | 8. **FundingRateFacet** 20 | 9. **ForceActionsFacet** 21 | 10. **SettlementFacet** 22 | 11. **PartyBPositionActionsFacet** 23 | 12. **PartyBQuoteActionsFacet** 24 | 13. **PartyBGroupActionsFacet** 25 | 26 | There are also some additional second-layer contracts required by hedgers and frontends: 27 | 28 | 1. **MultiAccount**: 29 | This contract allows each wallet to have multiple accounts within the system. Features like instant open/close and 30 | stop-loss bots require the `delegateAccess` feature provided by this contract. 31 | 32 | 2. **SymmioPartyB**: 33 | This contract enables hedgers to have multiple private keys behind their bots. 34 | 35 | ## Getting Started 36 | 37 | This project uses [Hardhat](https://hardhat.org/). You can compile the code with: 38 | 39 | ```bash 40 | npx hardhat compile 41 | ``` 42 | 43 | And you can run tests like this: 44 | 45 | ```bash 46 | ./utils/runTests.sh 47 | ``` 48 | 49 | The reason we cannot simply use `npx hardhat test` is that there are some Muon signature verification parts in the code 50 | that need to be commented out for the tests to run without issues. This script automates that task. 51 | 52 | ## Documentation 53 | 54 | For detailed technical documentation, visit: 55 | 56 | [https://docs.symm.io/protocol-architecture/technical-documentation](https://docs.symm.io/protocol-architecture/technical-documentation) 57 | 58 | ## License 59 | 60 | SYMM-Core-Business-Source-License-1.1 61 | 62 | For more information, see https://docs.symm.io/legal-disclaimer/license -------------------------------------------------------------------------------- /contracts/Diamond.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity >=0.8.18; 3 | 4 | /******************************************************************************\ 5 | * Author: Nick Mudge (https://twitter.com/mudgen) 6 | * EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535 7 | /******************************************************************************/ 8 | 9 | import { LibDiamond } from "./libraries/LibDiamond.sol"; 10 | import { IDiamondCut } from "./facets/DiamondCut/IDiamondCut.sol"; 11 | 12 | contract Diamond { 13 | receive() external payable {} 14 | 15 | constructor(address _contractOwner, address _diamondCutFacet) payable { 16 | LibDiamond.setContractOwner(_contractOwner); 17 | 18 | // Add the diamondCut external function from the diamondCutFacet 19 | IDiamondCut.FacetCut[] memory cut = new IDiamondCut.FacetCut[](1); 20 | bytes4[] memory functionSelectors = new bytes4[](1); 21 | functionSelectors[0] = IDiamondCut.diamondCut.selector; 22 | cut[0] = IDiamondCut.FacetCut({ 23 | facetAddress: _diamondCutFacet, 24 | action: IDiamondCut.FacetCutAction.Add, 25 | functionSelectors: functionSelectors 26 | }); 27 | LibDiamond.diamondCut(cut, address(0), ""); 28 | } 29 | 30 | // Find facet for function that is called and execute the 31 | // function if a facet is found and return any value. 32 | fallback() external payable { 33 | LibDiamond.DiamondStorage storage ds; 34 | bytes32 position = LibDiamond.DIAMOND_STORAGE_POSITION; 35 | // get diamond storage 36 | assembly { 37 | ds.slot := position 38 | } 39 | // get facet from function selector 40 | address facet = ds.facetAddressAndSelectorPosition[msg.sig].facetAddress; 41 | require(facet != address(0), "Diamond: Function does not exist"); 42 | // Execute external function from facet using delegatecall and return any value. 43 | assembly { 44 | // copy function selector and any arguments 45 | calldatacopy(0, 0, calldatasize()) 46 | // execute function call using the facet 47 | let result := delegatecall(gas(), facet, 0, calldatasize(), 0, 0) 48 | // get any return value 49 | returndatacopy(0, 0, returndatasize()) 50 | // return any return value or error back to the caller 51 | switch result 52 | case 0 { 53 | revert(0, returndatasize()) 54 | } 55 | default { 56 | return(0, returndatasize()) 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /contracts/FakeStableCoin.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity >=0.8.18; 3 | 4 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 5 | 6 | contract FakeStablecoin is ERC20 { 7 | constructor() ERC20("FakeStablecoin", "FUSD") {} 8 | 9 | function mint(address to, uint256 amount) external { 10 | _mint(to, amount); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /contracts/SymmioTimelockController.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity ^0.8.0; 6 | 7 | import "@openzeppelin/contracts/governance/TimelockController.sol"; 8 | 9 | contract SymmioTimelockController is TimelockController { 10 | constructor( 11 | uint256 minDelay, 12 | address[] memory proposers, 13 | address[] memory executors, 14 | address admin 15 | ) TimelockController(minDelay, proposers, executors, admin) {} 16 | } 17 | -------------------------------------------------------------------------------- /contracts/facets/Account/IAccountEvents.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity >=0.8.18; 6 | 7 | interface IAccountEvents { 8 | event Deposit(address sender, address user, uint256 amount); 9 | event Withdraw(address sender, address user, uint256 amount); 10 | event AllocatePartyA(address user, uint256 amount, uint256 newAllocatedBalance); 11 | event AllocatePartyA(address user, uint256 amount); // For backward compatibility, will be removed in future 12 | event DeallocatePartyA(address user, uint256 amount, uint256 newAllocatedBalance); 13 | event DeallocatePartyA(address user, uint256 amount); // For backward compatibility, will be removed in future 14 | event InternalTransfer(address sender, address user, uint256 userNewAllocatedBalance, uint256 amount); 15 | event AllocateForPartyB(address partyB, address partyA, uint256 amount, uint256 newAllocatedBalance); 16 | event AllocateForPartyB(address partyB, address partyA, uint256 amount); // For backward compatibility, will be removed in future 17 | event DeallocateForPartyB(address partyB, address partyA, uint256 amount, uint256 newAllocatedBalance); 18 | event DeallocateForPartyB(address partyB, address partyA, uint256 amount); // For backward compatibility, will be removed in future 19 | event TransferAllocation( 20 | uint256 amount, 21 | address origin, 22 | uint256 originNewAllocatedBalance, 23 | address recipient, 24 | uint256 recipientNewAllocatedBalance 25 | ); 26 | event TransferAllocation(uint256 amount, address origin, address recipient); // For backward compatibility, will be removed in future 27 | event DepositToReserveVault(address sender, address partyB, uint256 amount); 28 | event WithdrawFromReserveVault(address partyB, uint256 amount); 29 | } 30 | -------------------------------------------------------------------------------- /contracts/facets/Account/IAccountFacet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity >=0.8.18; 6 | 7 | import "./IAccountEvents.sol"; 8 | import "../../storages/MuonStorage.sol"; 9 | 10 | interface IAccountFacet is IAccountEvents { 11 | //Party A 12 | function deposit(uint256 amount) external; 13 | 14 | function depositFor(address user, uint256 amount) external; 15 | 16 | function withdraw(uint256 amount) external; 17 | 18 | function withdrawTo(address user, uint256 amount) external; 19 | 20 | function allocate(uint256 amount) external; 21 | 22 | function depositAndAllocate(uint256 amount) external; 23 | 24 | function deallocate(uint256 amount, SingleUpnlSig memory upnlSig) external; 25 | 26 | function internalTransfer(address user, uint256 amount) external; 27 | 28 | // PartyB 29 | function allocateForPartyB(uint256 amount, address partyA) external; 30 | 31 | function deallocateForPartyB(uint256 amount, address partyA, SingleUpnlSig memory upnlSig) external; 32 | 33 | function transferAllocation(uint256 amount, address origin, address recipient, SingleUpnlSig memory upnlSig) external; 34 | 35 | function depositToReserveVault(uint256 amount, address partyB) external; 36 | 37 | function withdrawFromReserveVault(uint256 amount) external; 38 | } 39 | -------------------------------------------------------------------------------- /contracts/facets/Bridge/BridgeFacet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity >=0.8.18; 6 | 7 | import "../../utils/Accessibility.sol"; 8 | import "../../utils/Pausable.sol"; 9 | import "./BridgeFacetImpl.sol"; 10 | import "./IBridgeFacet.sol"; 11 | 12 | contract BridgeFacet is Accessibility, Pausable, IBridgeFacet { 13 | /// @notice Transfers a specified amount to the designated bridge address. 14 | /// @param amount The precise amount to be transferred, specified in decimal units. 15 | /// @param bridgeAddress The address of the bridge to which the collateral will be transferred. 16 | function transferToBridge(uint256 amount, address bridgeAddress) external whenNotAccountingPaused notSuspended(msg.sender) { 17 | uint256 transactionId = BridgeFacetImpl.transferToBridge(msg.sender, amount, bridgeAddress); 18 | emit TransferToBridge(msg.sender, amount, bridgeAddress, transactionId); 19 | } 20 | 21 | /// @notice Withdraws the received bridge value associated with a specific transaction ID. 22 | /// @param transactionId The ID of the transaction for which the received bridge value will be withdrawn. 23 | function withdrawReceivedBridgeValue(uint256 transactionId) external whenNotAccountingPaused notSuspended(msg.sender) { 24 | BridgeFacetImpl.withdrawReceivedBridgeValue(transactionId); 25 | emit WithdrawReceivedBridgeValue(transactionId); 26 | } 27 | 28 | /// @notice Withdraws the received bridge values associated with multiple transaction IDs. 29 | /// @param transactionIds An array of transaction IDs for which the received bridge values will be withdrawn. 30 | function withdrawReceivedBridgeValues(uint256[] memory transactionIds) external whenNotAccountingPaused notSuspended(msg.sender) { 31 | BridgeFacetImpl.withdrawReceivedBridgeValues(transactionIds); 32 | emit WithdrawReceivedBridgeValues(transactionIds); 33 | } 34 | 35 | /// @notice Suspends a specific bridge transaction. 36 | /// @param transactionId The transaction ID of the bridge transaction to be suspended. 37 | function suspendBridgeTransaction(uint256 transactionId) external onlyRole(LibAccessibility.SUSPENDER_ROLE) { 38 | BridgeFacetImpl.suspendBridgeTransaction(transactionId); 39 | emit SuspendBridgeTransaction(transactionId); 40 | } 41 | 42 | /// @notice Restores a previously suspended bridge transaction and updates the valid transaction amount. 43 | /// @param transactionId The transaction ID of the bridge transaction to be restored. 44 | /// @param validAmount The validated amount to be associated with the restored transaction. 45 | function restoreBridgeTransaction(uint256 transactionId, uint256 validAmount) external onlyRole(LibAccessibility.DISPUTE_ROLE) { 46 | BridgeFacetImpl.restoreBridgeTransaction(transactionId, validAmount); 47 | emit RestoreBridgeTransaction(transactionId, validAmount); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /contracts/facets/Bridge/IBridgeEvents.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity >=0.8.18; 6 | 7 | interface IBridgeEvents { 8 | event TransferToBridge(address user, uint256 amount, address bridgeAddress, uint256 transactionId); 9 | event WithdrawReceivedBridgeValue(uint256 transactionId); 10 | event SuspendBridgeTransaction(uint256 transactionId); 11 | event RestoreBridgeTransaction(uint256 transactionId, uint256 validAmount); 12 | event WithdrawReceivedBridgeValues(uint256[] transactionIds); 13 | } 14 | -------------------------------------------------------------------------------- /contracts/facets/Bridge/IBridgeFacet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity >=0.8.18; 6 | 7 | import "./IBridgeEvents.sol"; 8 | 9 | interface IBridgeFacet is IBridgeEvents { 10 | function transferToBridge(uint256 amount, address bridgeAddress) external; 11 | 12 | function suspendBridgeTransaction(uint256 transactionId) external; 13 | 14 | function restoreBridgeTransaction(uint256 transactionId, uint256 validAmount) external; 15 | 16 | function withdrawReceivedBridgeValue(uint256 transactionId) external; 17 | 18 | function withdrawReceivedBridgeValues(uint256[] memory transactionIds) external; 19 | } 20 | -------------------------------------------------------------------------------- /contracts/facets/DiamondCut/DiamondCutFacet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity >=0.8.18; 3 | 4 | /******************************************************************************\ 5 | * Author: Nick Mudge (https://twitter.com/mudgen) 6 | * EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535 7 | /******************************************************************************/ 8 | 9 | import { IDiamondCut } from "./IDiamondCut.sol"; 10 | import { LibDiamond } from "../../libraries/LibDiamond.sol"; 11 | 12 | contract DiamondCutFacet is IDiamondCut { 13 | /// @notice Add/replace/remove any number of functions and optionally execute a function with delegatecall 14 | /// @param _diamondCut Contains the facet addresses and function selectors 15 | /// @param _init The address of the contract or facet to execute _calldata 16 | /// @param _calldata A function call, including function selector and arguments _calldata is executed with delegatecall on _init 17 | function diamondCut(FacetCut[] calldata _diamondCut, address _init, bytes calldata _calldata) external override { 18 | LibDiamond.enforceIsContractOwner(); 19 | LibDiamond.diamondCut(_diamondCut, _init, _calldata); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /contracts/facets/DiamondCut/IDiamondCut.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity >=0.8.18; 3 | 4 | /******************************************************************************\ 5 | * Author: Nick Mudge (https://twitter.com/mudgen) 6 | * EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535 7 | /******************************************************************************/ 8 | 9 | interface IDiamondCut { 10 | // Add=0, Replace=1, Remove=2 11 | enum FacetCutAction { 12 | Add, 13 | Replace, 14 | Remove 15 | } 16 | 17 | struct FacetCut { 18 | address facetAddress; 19 | FacetCutAction action; 20 | bytes4[] functionSelectors; 21 | } 22 | 23 | /// @notice Add/replace/remove any number of functions and optionally execute 24 | /// a function with delegatecall 25 | /// @param _diamondCut Contains the facet addresses and function selectors 26 | /// @param _init The address of the contract or facet to execute _calldata 27 | /// @param _calldata A function call, including function selector and arguments 28 | /// _calldata is executed with delegatecall on _init 29 | function diamondCut(FacetCut[] calldata _diamondCut, address _init, bytes calldata _calldata) external; 30 | 31 | event DiamondCut(FacetCut[] _diamondCut, address _init, bytes _calldata); 32 | } 33 | -------------------------------------------------------------------------------- /contracts/facets/DiamondLoup/IDiamondLoupe.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity >=0.8.18; 3 | 4 | /******************************************************************************\ 5 | * Author: Nick Mudge (https://twitter.com/mudgen) 6 | * EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535 7 | /******************************************************************************/ 8 | 9 | // A loupe is a small magnifying glass used to look at diamonds. 10 | // These functions look at diamonds 11 | interface IDiamondLoupe { 12 | struct Facet { 13 | address facetAddress; 14 | bytes4[] functionSelectors; 15 | } 16 | 17 | /// @notice Gets all facet addresses and their four byte function selectors. 18 | /// @return facets_ Facet 19 | function facets() external view returns (Facet[] memory facets_); 20 | 21 | /// @notice Gets all the function selectors supported by a specific facet. 22 | /// @param _facet The facet address. 23 | /// @return facetFunctionSelectors_ 24 | function facetFunctionSelectors(address _facet) external view returns (bytes4[] memory facetFunctionSelectors_); 25 | 26 | /// @notice Get all the facet addresses used by a diamond. 27 | /// @return facetAddresses_ 28 | function facetAddresses() external view returns (address[] memory facetAddresses_); 29 | 30 | /// @notice Gets the facet that supports the given selector. 31 | /// @dev If facet is not found return address(0). 32 | /// @param _functionSelector The function selector. 33 | /// @return facetAddress_ The facet address. 34 | function facetAddress(bytes4 _functionSelector) external view returns (address facetAddress_); 35 | } 36 | -------------------------------------------------------------------------------- /contracts/facets/ForceActions/ForceActionsFacetEvents.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity >=0.8.18; 6 | 7 | import "../../storages/QuoteStorage.sol"; 8 | 9 | interface ForceActionsFacetEvents { 10 | event ForceCancelQuote(uint256 quoteId, QuoteStatus quoteStatus); 11 | event ForceCancelCloseRequest(uint256 quoteId, QuoteStatus quoteStatus, uint256 closeId); 12 | event ForceCancelCloseRequest(uint256 quoteId, QuoteStatus quoteStatus); // For backward compatibility, will be removed in future 13 | event ForceClosePosition( 14 | uint256 quoteId, 15 | address partyA, 16 | address partyB, 17 | uint256 filledAmount, 18 | uint256 closedPrice, 19 | QuoteStatus quoteStatus, 20 | uint256 closeId 21 | ); 22 | event ForceClosePosition(uint256 quoteId, address partyA, address partyB, uint256 filledAmount, uint256 closedPrice, QuoteStatus quoteStatus); // For backward compatibility, will be removed in future 23 | } 24 | -------------------------------------------------------------------------------- /contracts/facets/ForceActions/IForceActionsFacet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity >=0.8.18; 6 | 7 | import "../../storages/MuonStorage.sol"; 8 | import "./ForceActionsFacetEvents.sol"; 9 | 10 | interface IForceActionsFacet is ForceActionsFacetEvents { 11 | function forceCancelQuote(uint256 quoteId) external; 12 | 13 | function forceCancelCloseRequest(uint256 quoteId) external; 14 | 15 | function forceClosePosition(uint256 quoteId, HighLowPriceSig memory sig) external; 16 | 17 | function settleAndForceClosePosition( 18 | uint256 quoteId, 19 | HighLowPriceSig memory highLowPriceSig, 20 | SettlementSig memory settleSig, 21 | uint256[] memory updatedPrices 22 | ) external; 23 | } 24 | -------------------------------------------------------------------------------- /contracts/facets/FundingRate/FundingRateFacet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity >=0.8.18; 6 | 7 | import "./FundingRateFacetImpl.sol"; 8 | import "../../utils/Pausable.sol"; 9 | import "../../utils/Accessibility.sol"; 10 | import "./IFundingRateFacet.sol"; 11 | 12 | contract FundingRateFacet is Accessibility, Pausable, IFundingRateFacet { 13 | /// @notice Charges funding rates for a given Party A position. 14 | /// @param partyA The address of Party A. 15 | /// @param quoteIds An array of quote IDs that we are about to get fudning for. 16 | /// @param rates An array of funding rates. 17 | /// @param upnlSig The Muon signature for upnl of both parties. 18 | function chargeFundingRate( 19 | address partyA, 20 | uint256[] memory quoteIds, 21 | int256[] memory rates, 22 | PairUpnlSig memory upnlSig 23 | ) external whenNotPartyBActionsPaused notLiquidatedPartyA(partyA) { 24 | FundingRateFacetImpl.chargeFundingRate(partyA, quoteIds, rates, upnlSig); 25 | emit ChargeFundingRate(msg.sender, partyA, quoteIds, rates); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /contracts/facets/FundingRate/IFundingRateEvents.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity >=0.8.18; 6 | 7 | interface IFundingRateEvents { 8 | event ChargeFundingRate(address partyB, address partyA, uint256[] quoteIds, int256[] rates); 9 | } 10 | -------------------------------------------------------------------------------- /contracts/facets/FundingRate/IFundingRateFacet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity >=0.8.18; 6 | import "./IFundingRateEvents.sol"; 7 | import "../../storages/MuonStorage.sol"; 8 | 9 | interface IFundingRateFacet is IFundingRateEvents { 10 | function chargeFundingRate(address partyA, uint256[] memory quoteIds, int256[] memory rates, PairUpnlSig memory upnlSig) external; 11 | } 12 | -------------------------------------------------------------------------------- /contracts/facets/PartyA/IPartyAEvents.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity >=0.8.18; 6 | 7 | import "../../storages/QuoteStorage.sol"; 8 | import "../../interfaces/IPartiesEvents.sol"; 9 | 10 | interface IPartyAEvents is IPartiesEvents { 11 | event RequestToCancelQuote(address partyA, address partyB, QuoteStatus quoteStatus, uint256 quoteId); 12 | event RequestToClosePosition( 13 | address partyA, 14 | address partyB, 15 | uint256 quoteId, 16 | uint256 closePrice, 17 | uint256 quantityToClose, 18 | OrderType orderType, 19 | uint256 deadline, 20 | QuoteStatus quoteStatus, 21 | uint256 closeId 22 | ); 23 | event RequestToClosePosition( 24 | address partyA, 25 | address partyB, 26 | uint256 quoteId, 27 | uint256 closePrice, 28 | uint256 quantityToClose, 29 | OrderType orderType, 30 | uint256 deadline, 31 | QuoteStatus quoteStatus 32 | ); // For backward compatibility, will be removed in future 33 | event RequestToCancelCloseRequest(address partyA, address partyB, uint256 quoteId, QuoteStatus quoteStatus, uint256 closeId); 34 | event RequestToCancelCloseRequest(address partyA, address partyB, uint256 quoteId, QuoteStatus quoteStatus); // For backward compatibility, will be removed in future 35 | } 36 | -------------------------------------------------------------------------------- /contracts/facets/PartyA/IPartyAFacet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity >=0.8.18; 6 | 7 | import "./IPartyAEvents.sol"; 8 | import "../../storages/MuonStorage.sol"; 9 | 10 | interface IPartyAFacet is IPartyAEvents { 11 | function sendQuote( 12 | address[] memory partyBsWhiteList, 13 | uint256 symbolId, 14 | PositionType positionType, 15 | OrderType orderType, 16 | uint256 price, 17 | uint256 quantity, 18 | uint256 cva, 19 | uint256 lf, 20 | uint256 partyAmm, 21 | uint256 partyBmm, 22 | uint256 maxFundingRate, 23 | uint256 deadline, 24 | SingleUpnlAndPriceSig memory upnlSig 25 | ) external; 26 | 27 | function sendQuoteWithAffiliate( 28 | address[] memory partyBsWhiteList, 29 | uint256 symbolId, 30 | PositionType positionType, 31 | OrderType orderType, 32 | uint256 price, 33 | uint256 quantity, 34 | uint256 cva, 35 | uint256 lf, 36 | uint256 partyAmm, 37 | uint256 partyBmm, 38 | uint256 maxFundingRate, 39 | uint256 deadline, 40 | address affiliate, 41 | SingleUpnlAndPriceSig memory upnlSig 42 | ) external returns (uint256); 43 | 44 | function expireQuote(uint256[] memory expiredQuoteIds) external; 45 | 46 | function requestToCancelQuote(uint256 quoteId) external; 47 | 48 | function requestToClosePosition(uint256 quoteId, uint256 closePrice, uint256 quantityToClose, OrderType orderType, uint256 deadline) external; 49 | 50 | function requestToCancelCloseRequest(uint256 quoteId) external; 51 | } 52 | -------------------------------------------------------------------------------- /contracts/facets/PartyBGroupActions/IPartyBGroupActionsFacet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity >=0.8.18; 6 | 7 | import "../PartyBQuoteActions/IPartyBQuoteActionsEvents.sol"; 8 | 9 | interface IPartyBGroupActionsFacet is IPartyBQuoteActionsEvents { 10 | function lockAndOpenQuote( 11 | uint256 quoteId, 12 | uint256 filledAmount, 13 | uint256 openedPrice, 14 | SingleUpnlSig memory upnlSig, 15 | PairUpnlAndPriceSig memory pairUpnlSig 16 | ) external; 17 | } 18 | -------------------------------------------------------------------------------- /contracts/facets/PartyBGroupActions/PartyBGroupActionsFacet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity >=0.8.18; 6 | 7 | import "./IPartyBGroupActionsFacet.sol"; 8 | import "../PartyBPositionActions/PartyBPositionActionsFacetImpl.sol"; 9 | import "../PartyBQuoteActions/PartyBQuoteActionsFacetImpl.sol"; 10 | import "../../utils/Accessibility.sol"; 11 | import "../../utils/Pausable.sol"; 12 | 13 | contract PartyBGroupActionsFacet is Accessibility, Pausable, IPartyBGroupActionsFacet { 14 | 15 | /** 16 | * @notice Locks and opens the specified quote with the provided details and signatures. 17 | * @param quoteId The ID of the quote to be locked and opened. 18 | * @param filledAmount PartyB has the option to open the position with either the full amount requested by the user or a specific fraction of it 19 | * @param openedPrice The price at which the position is opened. 20 | * @param upnlSig The Muon signature containing the single UPNL value used to lock the quote. 21 | * @param pairUpnlSig The Muon signature containing the pair UPNL and price values used to open the position. 22 | */ 23 | function lockAndOpenQuote( 24 | uint256 quoteId, 25 | uint256 filledAmount, 26 | uint256 openedPrice, 27 | SingleUpnlSig memory upnlSig, 28 | PairUpnlAndPriceSig memory pairUpnlSig 29 | ) external whenNotPartyBActionsPaused onlyPartyB notLiquidated(quoteId) { 30 | Quote storage quote = QuoteStorage.layout().quotes[quoteId]; 31 | PartyBQuoteActionsFacetImpl.lockQuote(quoteId, upnlSig); 32 | emit LockQuote(quote.partyB, quoteId); 33 | uint256 newId = PartyBPositionActionsFacetImpl.openPosition(quoteId, filledAmount, openedPrice, pairUpnlSig); 34 | emit OpenPosition(quoteId, quote.partyA, quote.partyB, filledAmount, openedPrice); 35 | if (newId != 0) { 36 | Quote storage newQuote = QuoteStorage.layout().quotes[newId]; 37 | if (newQuote.quoteStatus == QuoteStatus.PENDING) { 38 | emit SendQuote( 39 | newQuote.partyA, 40 | newQuote.id, 41 | newQuote.partyBsWhiteList, 42 | newQuote.symbolId, 43 | newQuote.positionType, 44 | newQuote.orderType, 45 | newQuote.requestedOpenPrice, 46 | newQuote.marketPrice, 47 | newQuote.quantity, 48 | newQuote.lockedValues.cva, 49 | newQuote.lockedValues.lf, 50 | newQuote.lockedValues.partyAmm, 51 | newQuote.lockedValues.partyBmm, 52 | newQuote.tradingFee, 53 | newQuote.deadline 54 | ); 55 | } else if (newQuote.quoteStatus == QuoteStatus.CANCELED) { 56 | emit AcceptCancelRequest(newQuote.id, QuoteStatus.CANCELED); 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /contracts/facets/PartyBPositionActions/IPartyBPositionActionsEvents.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity >=0.8.18; 6 | 7 | import "../../interfaces/IPartiesEvents.sol"; 8 | 9 | interface IPartyBPositionActionsEvents is IPartiesEvents { 10 | event AcceptCancelCloseRequest(uint256 quoteId, QuoteStatus quoteStatus, uint256 closeId); 11 | event AcceptCancelCloseRequest(uint256 quoteId, QuoteStatus quoteStatus); // For backward compatibility, will be removed in future 12 | event EmergencyClosePosition( 13 | uint256 quoteId, 14 | address partyA, 15 | address partyB, 16 | uint256 filledAmount, 17 | uint256 closedPrice, 18 | QuoteStatus quoteStatus, 19 | uint256 closeId 20 | ); 21 | event EmergencyClosePosition(uint256 quoteId, address partyA, address partyB, uint256 filledAmount, uint256 closedPrice, QuoteStatus quoteStatus); // For backward compatibility, will be removed in future 22 | } 23 | -------------------------------------------------------------------------------- /contracts/facets/PartyBPositionActions/IPartyBPositionActionsFacet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity >=0.8.18; 6 | 7 | import "./IPartyBPositionActionsEvents.sol"; 8 | 9 | interface IPartyBPositionActionsFacet is IPartyBPositionActionsEvents { 10 | function openPosition(uint256 quoteId, uint256 filledAmount, uint256 openedPrice, PairUpnlAndPriceSig memory upnlSig) external; 11 | 12 | function fillCloseRequest(uint256 quoteId, uint256 filledAmount, uint256 closedPrice, PairUpnlAndPriceSig memory upnlSig) external; 13 | 14 | function acceptCancelCloseRequest(uint256 quoteId) external; 15 | 16 | function emergencyClosePosition(uint256 quoteId, PairUpnlAndPriceSig memory upnlSig) external; 17 | } 18 | -------------------------------------------------------------------------------- /contracts/facets/PartyBQuoteActions/IPartyBQuoteActionsEvents.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity >=0.8.18; 6 | 7 | import "../../interfaces/IPartiesEvents.sol"; 8 | 9 | interface IPartyBQuoteActionsEvents is IPartiesEvents { 10 | event LockQuote(address partyB, uint256 quoteId); 11 | event AllocatePartyB(address partyB, address partyA, uint256 amount); 12 | event UnlockQuote(address partyB, uint256 quoteId, QuoteStatus quoteStatus); 13 | } 14 | -------------------------------------------------------------------------------- /contracts/facets/PartyBQuoteActions/IPartyBQuoteActionsFacet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity >=0.8.18; 6 | 7 | import "./IPartyBQuoteActionsEvents.sol"; 8 | 9 | interface IPartyBQuoteActionsFacet is IPartyBQuoteActionsEvents { 10 | function lockQuote(uint256 quoteId, SingleUpnlSig memory upnlSig) external; 11 | 12 | function unlockQuote(uint256 quoteId) external; 13 | 14 | function acceptCancelRequest(uint256 quoteId) external; 15 | } 16 | -------------------------------------------------------------------------------- /contracts/facets/PartyBQuoteActions/PartyBQuoteActionsFacet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity >=0.8.18; 6 | import "./PartyBQuoteActionsFacetImpl.sol"; 7 | import "../../utils/Accessibility.sol"; 8 | import "../../utils/Pausable.sol"; 9 | import "./IPartyBQuoteActionsFacet.sol"; 10 | 11 | contract PartyBQuoteActionsFacet is Accessibility, Pausable, IPartyBQuoteActionsFacet { 12 | using LockedValuesOps for LockedValues; 13 | 14 | /** 15 | * @notice Once a user issues a quote, any PartyB can secure it by providing sufficient funds, based on their estimated profit and loss from opening the position. 16 | * @param quoteId The ID of the quote to be locked. 17 | * @param upnlSig The Muon signature containing the upnl value used to lock the quote. 18 | */ 19 | function lockQuote(uint256 quoteId, SingleUpnlSig memory upnlSig) external whenNotPartyBActionsPaused onlyPartyB notLiquidated(quoteId) { 20 | PartyBQuoteActionsFacetImpl.lockQuote(quoteId, upnlSig); 21 | Quote storage quote = QuoteStorage.layout().quotes[quoteId]; 22 | emit LockQuote(quote.partyB, quoteId); 23 | } 24 | 25 | /** 26 | * @notice Unlocks the specified quote. 27 | * @param quoteId The ID of the quote to be unlocked. 28 | */ 29 | function unlockQuote(uint256 quoteId) external whenNotPartyBActionsPaused onlyPartyBOfQuote(quoteId) notLiquidated(quoteId) { 30 | QuoteStatus res = PartyBQuoteActionsFacetImpl.unlockQuote(quoteId); 31 | if (res == QuoteStatus.EXPIRED) { 32 | emit ExpireQuoteOpen(res, quoteId); 33 | } else if (res == QuoteStatus.PENDING) { 34 | emit UnlockQuote(msg.sender, quoteId, QuoteStatus.PENDING); 35 | } 36 | } 37 | 38 | /** 39 | * @notice Accepts the cancellation request for the specified quote. 40 | * @param quoteId The ID of the quote for which the cancellation request is accepted. 41 | */ 42 | function acceptCancelRequest(uint256 quoteId) external whenNotPartyBActionsPaused onlyPartyBOfQuote(quoteId) notLiquidated(quoteId) { 43 | PartyBQuoteActionsFacetImpl.acceptCancelRequest(quoteId); 44 | emit AcceptCancelRequest(quoteId, QuoteStatus.CANCELED); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /contracts/facets/PartyBQuoteActions/PartyBQuoteActionsFacetImpl.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity >=0.8.18; 6 | 7 | import "../../libraries/muon/LibMuonPartyB.sol"; 8 | import "../../libraries/LibQuote.sol"; 9 | import "../../libraries/LibPartyBQuoteActions.sol"; 10 | 11 | library PartyBQuoteActionsFacetImpl { 12 | using LockedValuesOps for LockedValues; 13 | 14 | function lockQuote(uint256 quoteId, SingleUpnlSig memory upnlSig) internal { 15 | QuoteStorage.Layout storage quoteLayout = QuoteStorage.layout(); 16 | Quote storage quote = quoteLayout.quotes[quoteId]; 17 | LibMuonPartyB.verifyPartyBUpnl(upnlSig, msg.sender, quote.partyA); 18 | int256 availableBalance = LibAccount.partyBAvailableForQuote(upnlSig.upnl, msg.sender, quote.partyA); 19 | require(availableBalance >= 0, "PartyBFacet: Available balance is lower than zero"); 20 | require(uint256(availableBalance) >= quote.lockedValues.totalForPartyB(), "PartyBFacet: insufficient available balance"); 21 | LibPartyBQuoteActions.lockQuote(quoteId); 22 | } 23 | 24 | function unlockQuote(uint256 quoteId) internal returns (QuoteStatus) { 25 | AccountStorage.Layout storage accountLayout = AccountStorage.layout(); 26 | QuoteStorage.Layout storage quoteLayout = QuoteStorage.layout(); 27 | 28 | Quote storage quote = quoteLayout.quotes[quoteId]; 29 | require(quote.quoteStatus == QuoteStatus.LOCKED, "PartyBFacet: Invalid state"); 30 | if (block.timestamp > quote.deadline) { 31 | QuoteStatus result = LibQuote.expireQuote(quoteId); 32 | return result; 33 | } else { 34 | quote.statusModifyTimestamp = block.timestamp; 35 | quote.quoteStatus = QuoteStatus.PENDING; 36 | accountLayout.partyBPendingLockedBalances[quote.partyB][quote.partyA].subQuote(quote); 37 | LibQuote.removeFromPartyBPendingQuotes(quote); 38 | quote.partyB = address(0); 39 | return QuoteStatus.PENDING; 40 | } 41 | } 42 | 43 | function acceptCancelRequest(uint256 quoteId) internal { 44 | AccountStorage.Layout storage accountLayout = AccountStorage.layout(); 45 | 46 | Quote storage quote = QuoteStorage.layout().quotes[quoteId]; 47 | require(quote.quoteStatus == QuoteStatus.CANCEL_PENDING, "PartyBFacet: Invalid state"); 48 | quote.statusModifyTimestamp = block.timestamp; 49 | quote.quoteStatus = QuoteStatus.CANCELED; 50 | accountLayout.pendingLockedBalances[quote.partyA].subQuote(quote); 51 | accountLayout.partyBPendingLockedBalances[quote.partyB][quote.partyA].subQuote(quote); 52 | 53 | // send trading Fee back to partyA 54 | uint256 fee = LibQuote.getTradingFee(quoteId); 55 | accountLayout.allocatedBalances[quote.partyA] += fee; 56 | emit SharedEvents.BalanceChangePartyA(quote.partyA, fee, SharedEvents.BalanceChangeType.PLATFORM_FEE_IN); 57 | 58 | LibQuote.removeFromPendingQuotes(quote); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /contracts/facets/Settlement/ISettlementFacet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity >=0.8.18; 6 | 7 | import "../../storages/MuonStorage.sol"; 8 | import "./SettlementFacetEvents.sol"; 9 | 10 | interface ISettlementFacet is SettlementFacetEvents { 11 | function settleUpnl(SettlementSig memory settleSig, uint256[] memory updatedPrices, address partyA) external; 12 | } 13 | -------------------------------------------------------------------------------- /contracts/facets/Settlement/SettlementFacet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity >=0.8.18; 6 | 7 | import "./ISettlementFacet.sol"; 8 | import "../../libraries/LibAccessibility.sol"; 9 | import "../../libraries/LibAccessibility.sol"; 10 | import "../../storages/MuonStorage.sol"; 11 | import "../../utils/Accessibility.sol"; 12 | import "../../utils/Pausable.sol"; 13 | import "./SettlementFacetImpl.sol"; 14 | 15 | contract SettlementFacet is Accessibility, Pausable, ISettlementFacet { 16 | /** 17 | * @notice Allows Party B to settle the upnl of party A position for the specified quotes. 18 | * @param settlementSig The data struct contains quoteIds and upnl of parties and market prices 19 | * @param updatedPrices New prices to be set as openedPrice for the specified quotes. 20 | * @param partyA Address of party A 21 | */ 22 | function settleUpnl( 23 | SettlementSig memory settlementSig, 24 | uint256[] memory updatedPrices, 25 | address partyA 26 | ) external whenNotPartyBActionsPaused onlyPartyB notLiquidatedPartyA(partyA) { 27 | uint256[] memory newPartyBsAllocatedBalances = SettlementFacetImpl.settleUpnl(settlementSig, updatedPrices, partyA); 28 | emit SettleUpnl( 29 | settlementSig.quotesSettlementsData, 30 | updatedPrices, 31 | partyA, 32 | AccountStorage.layout().allocatedBalances[partyA], 33 | newPartyBsAllocatedBalances 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /contracts/facets/Settlement/SettlementFacetEvents.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity >=0.8.18; 6 | 7 | import "../../storages/MuonStorage.sol"; 8 | 9 | interface SettlementFacetEvents { 10 | event SettleUpnl( 11 | QuoteSettlementData[] settlementData, 12 | uint256[] updatedPrices, 13 | address partyA, 14 | uint256 newPartyAAllocatedBalance, 15 | uint256[] newPartyBsAllocatedBalances 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /contracts/facets/Settlement/SettlementFacetImpl.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity >=0.8.18; 6 | 7 | import "../../libraries/muon/LibMuonSettlement.sol"; 8 | import "../../libraries/LibSettlement.sol"; 9 | 10 | library SettlementFacetImpl { 11 | function settleUpnl( 12 | SettlementSig memory settleSig, 13 | uint256[] memory updatedPrices, 14 | address partyA 15 | ) internal returns (uint256[] memory newPartyBsAllocatedBalances) { 16 | LibMuonSettlement.verifySettlement(settleSig, partyA); 17 | return LibSettlement.settleUpnl(settleSig, updatedPrices, partyA, false); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /contracts/facets/liquidation/ILiquidationEvents.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity >=0.8.18; 6 | import "../../interfaces/IPartiesEvents.sol"; 7 | 8 | interface ILiquidationEvents is IPartiesEvents { 9 | event LiquidatePartyA(address liquidator, address partyA, uint256 allocatedBalance, int256 upnl, int256 totalUnrealizedLoss, bytes liquidationId); 10 | event LiquidatePartyA(address liquidator, address partyA, uint256 allocatedBalance, int256 upnl, int256 totalUnrealizedLoss); // For backward compatibility, will be removed in future 11 | event DeferredLiquidatePartyA( 12 | address liquidator, 13 | address partyA, 14 | uint256 allocatedBalance, 15 | int256 upnl, 16 | int256 totalUnrealizedLoss, 17 | bytes liquidationId, 18 | uint256 liquidationBlockNumber, 19 | uint256 liquidationTimestamp, 20 | uint256 liquidationAllocatedBalance 21 | ); 22 | event LiquidatePositionsPartyA( 23 | address liquidator, 24 | address partyA, 25 | uint256[] quoteIds, 26 | uint256[] liquidatedAmounts, 27 | uint256[] closeIds, 28 | bytes liquidationId 29 | ); 30 | event LiquidatePositionsPartyA(address liquidator, address partyA, uint256[] quoteIds); // For backward compatibility, will be removed in future 31 | event LiquidatePendingPositionsPartyA(address liquidator, address partyA, uint256[] quoteIds, uint256[] liquidatedAmounts, bytes liquidationId); 32 | event LiquidatePendingPositionsPartyA(address liquidator, address partyA); // For backward compatibility, will be removed in future 33 | event SettlePartyALiquidation(address partyA, address[] partyBs, int256[] amounts, bytes liquidationId); 34 | event SettlePartyALiquidation(address partyA, address[] partyBs, int256[] amounts); // For backward compatibility, will be removed in future 35 | event LiquidationDisputed(address partyA, bytes liquidationId); 36 | event LiquidationDisputed(address partyA); // For backward compatibility, will be removed in future 37 | event ResolveLiquidationDispute(address partyA, address[] partyBs, int256[] amounts, bool disputed, bytes liquidationId); 38 | event ResolveLiquidationDispute(address partyA, address[] partyBs, int256[] amounts, bool disputed); // For backward compatibility, will be removed in future 39 | event FullyLiquidatedPartyA(address partyA, bytes liquidationId); 40 | event FullyLiquidatedPartyA(address partyA); // For backward compatibility, will be removed in future 41 | event LiquidatePositionsPartyB( 42 | address liquidator, 43 | address partyB, 44 | address partyA, 45 | uint256[] quoteIds, 46 | uint256[] liquidatedAmounts, 47 | uint256[] closeIds 48 | ); 49 | event LiquidatePositionsPartyB(address liquidator, address partyB, address partyA, uint256[] quoteIds); // For backward compatibility, will be removed in future 50 | event FullyLiquidatedPartyB(address partyB, address partyA); 51 | event SetSymbolsPrices(address liquidator, address partyA, uint256[] symbolIds, uint256[] prices, bytes liquidationId); 52 | event SetSymbolsPrices(address liquidator, address partyA, uint256[] symbolIds, uint256[] prices); // For backward compatibility, will be removed in future 53 | event DisputeForLiquidation(address liquidator, address partyA, bytes liquidationId); 54 | } 55 | -------------------------------------------------------------------------------- /contracts/facets/liquidation/ILiquidationFacet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity >=0.8.18; 6 | 7 | import "./ILiquidationEvents.sol"; 8 | import "../../storages/AccountStorage.sol"; 9 | import "../../storages/MuonStorage.sol"; 10 | 11 | interface ILiquidationFacet is ILiquidationEvents { 12 | function liquidatePartyA(address partyA, LiquidationSig memory liquidationSig) external; 13 | 14 | function setSymbolsPrice(address partyA, LiquidationSig memory liquidationSig) external; 15 | 16 | function deferredLiquidatePartyA(address partyA, DeferredLiquidationSig memory liquidationSig) external; 17 | 18 | function deferredSetSymbolsPrice(address partyA, DeferredLiquidationSig memory liquidationSig) external; 19 | 20 | function liquidatePendingPositionsPartyA(address partyA) external; 21 | 22 | function liquidatePositionsPartyA(address partyA, uint256[] memory quoteIds) external; 23 | 24 | function settlePartyALiquidation(address partyA, address[] memory partyBs) external; 25 | 26 | function resolveLiquidationDispute(address partyA, address[] memory partyBs, int256[] memory amounts, bool disputed) external; 27 | 28 | function liquidatePartyB(address partyB, address partyA, SingleUpnlSig memory upnlSig) external; 29 | 30 | function liquidatePositionsPartyB(address partyB, address partyA, QuotePriceSig memory priceSig) external; 31 | } 32 | -------------------------------------------------------------------------------- /contracts/helpers/NextQuoteIDVerifier.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity ^0.8.18; 6 | 7 | import "../interfaces/ISymmio.sol"; 8 | 9 | contract NextQuoteIDVerifier { 10 | ISymmio public symmioFacet; 11 | 12 | /** 13 | * @notice Sets the address of the symmio contract. 14 | * @param _symmioAddress Symmio address. 15 | */ 16 | constructor(address _symmioAddress) { 17 | require(_symmioAddress != address(0), "Invalid symmio address"); 18 | symmioFacet = ISymmio(_symmioAddress); 19 | } 20 | 21 | /** 22 | * @notice Verifies if the given quote ID is the next generated quote ID. 23 | * @param quoteId The quote ID to verify. 24 | */ 25 | function verifyNextQuoteId(uint256 quoteId) external view { 26 | uint256 lastQuoteId = symmioFacet.getNextQuoteId(); 27 | require(quoteId == lastQuoteId, "Invalid NextQuoteId"); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /contracts/helpers/SymmioInitHelper.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity >=0.8.18; 6 | 7 | import "@openzeppelin/contracts/access/AccessControlEnumerable.sol"; 8 | import "../interfaces/ISymmio.sol"; 9 | 10 | contract SymmioInitHelper is AccessControlEnumerable { 11 | bytes32 public constant SETTER_ROLE = keccak256("SETTER_ROLE"); 12 | 13 | address public symmioAddress; 14 | 15 | /** 16 | * @dev Constructor that sets the `symmioAddress` and assigns the admin and setter roles. 17 | * @param _symmioAddress Address to be set as `symmioAddress`. 18 | * @param admin Address to be given all roles. 19 | */ 20 | constructor(address _symmioAddress, address admin) { 21 | symmioAddress = _symmioAddress; 22 | 23 | _setupRole(DEFAULT_ADMIN_ROLE, admin); 24 | _setupRole(SETTER_ROLE, admin); 25 | } 26 | 27 | function setSymmioAddress(address _symmioAddress) external { 28 | require(hasRole(SETTER_ROLE, msg.sender), "SymmioInitHelper: Caller is not a setter"); 29 | symmioAddress = _symmioAddress; 30 | } 31 | 32 | function setCooldowns( 33 | uint256 deallocateCooldown, 34 | uint256 forceCancelCooldown, 35 | uint256 forceCancelCloseCooldown, 36 | uint256 forceCloseFirstCooldown, 37 | uint256 forceCloseSecondCooldown, 38 | uint256 forceClosePricePenalty, 39 | uint256 forceCloseMinSigPeriod, 40 | uint256 liquidationTimeout 41 | ) external onlyRole(SETTER_ROLE) { 42 | ISymmio(symmioAddress).setDeallocateCooldown(deallocateCooldown); 43 | ISymmio(symmioAddress).setForceCancelCooldown(forceCancelCooldown); 44 | ISymmio(symmioAddress).setForceCloseCooldowns(forceCloseFirstCooldown, forceCloseSecondCooldown); 45 | ISymmio(symmioAddress).setForceClosePricePenalty(forceClosePricePenalty); 46 | ISymmio(symmioAddress).setForceCancelCloseCooldown(forceCancelCloseCooldown); 47 | ISymmio(symmioAddress).setForceCloseMinSigPeriod(forceCloseMinSigPeriod); 48 | ISymmio(symmioAddress).setLiquidationTimeout(liquidationTimeout); 49 | } 50 | 51 | function setSymbolParameters( 52 | uint256 symbolId, 53 | bool isValid, 54 | uint256 maxLeverage, 55 | uint256 minAcceptableQuoteValue, 56 | uint256 minAcceptablePortionLF, 57 | uint256 tradingFee, 58 | uint256 fundingRateEpochDuration, 59 | uint256 fundingRateWindowTime 60 | ) external onlyRole(SETTER_ROLE) { 61 | ISymmio(symmioAddress).setSymbolValidationState(symbolId, isValid); 62 | ISymmio(symmioAddress).setSymbolMaxLeverage(symbolId, maxLeverage); 63 | ISymmio(symmioAddress).setSymbolAcceptableValues(symbolId, minAcceptableQuoteValue, minAcceptablePortionLF); 64 | ISymmio(symmioAddress).setSymbolTradingFee(symbolId, tradingFee); 65 | ISymmio(symmioAddress).setSymbolFundingState(symbolId, fundingRateEpochDuration, fundingRateWindowTime); 66 | } 67 | 68 | function setPlatformParameters( 69 | address partyB, 70 | uint256 muonAppId, 71 | address validGateway, 72 | PublicKey memory publicKey, 73 | address collateral, 74 | uint256 liquidatorShare, 75 | uint256 pendingQuotesValidLength, 76 | uint256 balanceLimitPerUser 77 | ) external onlyRole(SETTER_ROLE) { 78 | ISymmio(symmioAddress).registerPartyB(partyB); 79 | ISymmio(symmioAddress).setMuonIds(muonAppId, validGateway, publicKey); 80 | ISymmio(symmioAddress).setCollateral(collateral); 81 | ISymmio(symmioAddress).setLiquidatorShare(liquidatorShare); 82 | ISymmio(symmioAddress).setPendingQuotesValidLength(pendingQuotesValidLength); 83 | ISymmio(symmioAddress).setBalanceLimitPerUser(balanceLimitPerUser); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /contracts/interfaces/IERC165.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity >=0.8.18; 3 | 4 | interface IERC165 { 5 | /// @notice Query if a contract implements an interface 6 | /// @param interfaceId The interface identifier, as specified in ERC-165 7 | /// @dev Interface identification is specified in ERC-165. This function 8 | /// uses less than 30,000 gas. 9 | /// @return `true` if the contract implements `interfaceID` and 10 | /// `interfaceID` is not 0xffffffff, `false` otherwise 11 | function supportsInterface(bytes4 interfaceId) external view returns (bool); 12 | } 13 | -------------------------------------------------------------------------------- /contracts/interfaces/IMultiAccount.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity >=0.8.18; 6 | 7 | interface IMultiAccount { 8 | struct Account { 9 | address accountAddress; 10 | string name; 11 | } 12 | event SetAccountImplementation(bytes oldAddress, bytes newAddress); 13 | event SetAccountsAdmin(address oldAddress, address newAddress); 14 | event SetSymmioAddress(address oldAddress, address newAddress); 15 | event DeployContract(address sender, address contractAddress); 16 | event AddAccount(address user, address account, string name); 17 | event EditAccountName(address user, address account, string newName); 18 | event DepositForAccount(address user, address account, uint256 amount); 19 | event AllocateForAccount(address user, address account, uint256 amount); 20 | event WithdrawFromAccount(address user, address account, uint256 amount); 21 | event Call(address user, address account, bytes _callData, bool _success, bytes _resultData); 22 | event DelegateAccess(address account, address target, bytes4 selector, bool state); 23 | event DelegateAccesses(address account, address target, bytes4[] selector, bool state); 24 | event ProposeToRevokeAccesses(address account, address target, bytes4[] selector); 25 | event SetRevokeCooldown(uint256 oldCooldown, uint256 newCooldown); 26 | } 27 | -------------------------------------------------------------------------------- /contracts/interfaces/IPartiesEvents.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity >=0.8.18; 6 | 7 | import "../storages/QuoteStorage.sol"; 8 | import "../storages/MuonStorage.sol"; 9 | 10 | interface IPartiesEvents { 11 | event AcceptCancelRequest(uint256 quoteId, QuoteStatus quoteStatus); 12 | 13 | event SendQuote( 14 | address partyA, 15 | uint256 quoteId, 16 | address[] partyBsWhiteList, 17 | uint256 symbolId, 18 | PositionType positionType, 19 | OrderType orderType, 20 | uint256 price, 21 | uint256 marketPrice, 22 | uint256 quantity, 23 | uint256 cva, 24 | uint256 lf, 25 | uint256 partyAmm, 26 | uint256 partyBmm, 27 | uint256 tradingFee, 28 | uint256 deadline 29 | ); 30 | 31 | event ExpireQuote(QuoteStatus quoteStatus, uint256 quoteId); // For backward compatibility, will be removed in future 32 | 33 | event ExpireQuoteOpen(QuoteStatus quoteStatus, uint256 quoteId); 34 | 35 | event ExpireQuoteClose(QuoteStatus quoteStatus, uint256 quoteId, uint256 closeId); 36 | 37 | event OpenPosition(uint256 quoteId, address partyA, address partyB, uint256 filledAmount, uint256 openedPrice); 38 | 39 | event FillCloseRequest( 40 | uint256 quoteId, 41 | address partyA, 42 | address partyB, 43 | uint256 filledAmount, 44 | uint256 closedPrice, 45 | QuoteStatus quoteStatus, 46 | uint256 closeId 47 | ); 48 | 49 | event FillCloseRequest(uint256 quoteId, address partyA, address partyB, uint256 filledAmount, uint256 closedPrice, QuoteStatus quoteStatus); // For backward compatibility, will be removed in future 50 | 51 | event LiquidatePartyB(address liquidator, address partyB, address partyA, uint256 partyBAllocatedBalance, int256 upnl); 52 | } 53 | -------------------------------------------------------------------------------- /contracts/interfaces/ISymmio.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity >=0.8.18; 6 | 7 | import "../facets/Account/IAccountFacet.sol"; 8 | import "../facets/Control/IControlFacet.sol"; 9 | import "../facets/FundingRate/IFundingRateFacet.sol"; 10 | import "../facets/liquidation/ILiquidationFacet.sol"; 11 | import "../facets/PartyA/IPartyAFacet.sol"; 12 | import "../facets/Bridge/IBridgeFacet.sol"; 13 | import "../facets/ViewFacet/IViewFacet.sol"; 14 | import "../facets/DiamondCut/IDiamondCut.sol"; 15 | import "../facets/DiamondLoup/IDiamondLoupe.sol"; 16 | import "../facets/PartyBQuoteActions/IPartyBQuoteActionsFacet.sol"; 17 | import "../facets/PartyBPositionActions/IPartyBPositionActionsFacet.sol"; 18 | import "../facets/PartyBGroupActions/IPartyBGroupActionsFacet.sol"; 19 | import "../facets/ForceActions/IForceActionsFacet.sol"; 20 | import "../facets/Settlement/ISettlementFacet.sol"; 21 | 22 | interface ISymmio is 23 | IAccountFacet, 24 | IControlFacet, 25 | IFundingRateFacet, 26 | IBridgeFacet, 27 | ISettlementFacet, 28 | IForceActionsFacet, 29 | IPartyBQuoteActionsFacet, 30 | IPartyBGroupActionsFacet, 31 | IPartyBPositionActionsFacet, 32 | IPartyAFacet, 33 | ILiquidationFacet, 34 | IViewFacet, 35 | IDiamondCut, 36 | IDiamondLoupe 37 | { 38 | // Copied from SharedEvents library 39 | enum BalanceChangeType { 40 | ALLOCATE, 41 | DEALLOCATE, 42 | PLATFORM_FEE_IN, 43 | PLATFORM_FEE_OUT, 44 | REALIZED_PNL_IN, 45 | REALIZED_PNL_OUT, 46 | CVA_IN, 47 | CVA_OUT, 48 | LF_IN, 49 | LF_OUT, 50 | FUNDING_FEE_IN, 51 | FUNDING_FEE_OUT 52 | } 53 | 54 | // Copied from SharedEvents library 55 | event BalanceChangePartyA(address indexed partyA, uint256 amount, BalanceChangeType _type); 56 | 57 | // Copied from SharedEvents library 58 | event BalanceChangePartyB(address indexed partyB, address indexed partyA, uint256 amount, BalanceChangeType _type); 59 | } 60 | -------------------------------------------------------------------------------- /contracts/interfaces/ISymmioPartyA.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity >=0.8.18; 6 | 7 | interface ISymmioPartyA { 8 | function _approve(address token, uint256 amount) external; 9 | 10 | function _call(bytes calldata _callData) external returns (bool _success, bytes memory _resultData); 11 | 12 | function withdrawERC20(address token, uint256 amount) external; 13 | } 14 | -------------------------------------------------------------------------------- /contracts/libraries/DevLogging.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity >=0.8.18; 6 | 7 | library DevLogging { 8 | event LogUint(uint256 value); 9 | event LogInt(int256 value); 10 | event LogAddress(address value); 11 | event LogString(string value); 12 | } 13 | 14 | interface DevLoggingInterface { 15 | event LogUint(uint256 value); 16 | event LogInt(int256 value); 17 | event LogAddress(address value); 18 | event LogString(string value); 19 | } 20 | -------------------------------------------------------------------------------- /contracts/libraries/LibAccessibility.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity >=0.8.18; 6 | 7 | import "../storages/GlobalAppStorage.sol"; 8 | 9 | library LibAccessibility { 10 | bytes32 public constant DEFAULT_ADMIN_ROLE = keccak256("DEFAULT_ADMIN_ROLE"); 11 | bytes32 public constant MUON_SETTER_ROLE = keccak256("MUON_SETTER_ROLE"); 12 | bytes32 public constant SYMBOL_MANAGER_ROLE = keccak256("SYMBOL_MANAGER_ROLE"); 13 | bytes32 public constant SETTER_ROLE = keccak256("SETTER_ROLE"); 14 | bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); 15 | bytes32 public constant UNPAUSER_ROLE = keccak256("UNPAUSER_ROLE"); 16 | bytes32 public constant PARTY_B_MANAGER_ROLE = keccak256("PARTY_B_MANAGER_ROLE"); 17 | bytes32 public constant LIQUIDATOR_ROLE = keccak256("LIQUIDATOR_ROLE"); 18 | bytes32 public constant SUSPENDER_ROLE = keccak256("SUSPENDER_ROLE"); 19 | bytes32 public constant DISPUTE_ROLE = keccak256("DISPUTE_ROLE"); 20 | bytes32 public constant AFFILIATE_MANAGER_ROLE = keccak256("AFFILIATE_MANAGER_ROLE"); 21 | 22 | /** 23 | * @notice Checks if a user has a specific role. 24 | * @param user The address of the user. 25 | * @param role The role to check. 26 | * @return Whether the user has the specified role. 27 | */ 28 | function hasRole(address user, bytes32 role) internal view returns (bool) { 29 | GlobalAppStorage.Layout storage layout = GlobalAppStorage.layout(); 30 | return layout.hasRole[user][role]; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /contracts/libraries/LibPartyBQuoteActions.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity >=0.8.18; 6 | 7 | import "../storages/QuoteStorage.sol"; 8 | import "../storages/MAStorage.sol"; 9 | import "./LibAccount.sol"; 10 | import "./LibLockedValues.sol"; 11 | 12 | library LibPartyBQuoteActions { 13 | using LockedValuesOps for LockedValues; 14 | 15 | function lockQuote(uint256 quoteId) internal { 16 | QuoteStorage.Layout storage quoteLayout = QuoteStorage.layout(); 17 | AccountStorage.Layout storage accountLayout = AccountStorage.layout(); 18 | Quote storage quote = quoteLayout.quotes[quoteId]; 19 | require(quote.quoteStatus == QuoteStatus.PENDING, "PartyBFacet: Invalid state"); 20 | require(block.timestamp <= quote.deadline, "PartyBFacet: Quote is expired"); 21 | require(quoteId <= quoteLayout.lastId, "PartyBFacet: Invalid quoteId"); 22 | require(!MAStorage.layout().partyBLiquidationStatus[msg.sender][quote.partyA], "PartyBFacet: PartyB isn't solvent"); 23 | bool isValidPartyB; 24 | if (quote.partyBsWhiteList.length == 0) { 25 | require(msg.sender != quote.partyA, "PartyBFacet: PartyA can't be partyB too"); 26 | isValidPartyB = true; 27 | } else { 28 | for (uint8 index = 0; index < quote.partyBsWhiteList.length; index++) { 29 | if (msg.sender == quote.partyBsWhiteList[index]) { 30 | isValidPartyB = true; 31 | break; 32 | } 33 | } 34 | } 35 | require(isValidPartyB, "PartyBFacet: Sender isn't whitelisted"); 36 | quote.statusModifyTimestamp = block.timestamp; 37 | quote.quoteStatus = QuoteStatus.LOCKED; 38 | quote.partyB = msg.sender; 39 | // lock funds for partyB 40 | accountLayout.partyBPendingLockedBalances[msg.sender][quote.partyA].addQuote(quote); 41 | quoteLayout.partyBPendingQuotes[msg.sender][quote.partyA].push(quote.id); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /contracts/libraries/LibUtils.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity >=0.8.18; 6 | 7 | library LibUtils { 8 | /** 9 | * @notice Gets the index of an item in an array. 10 | * @param array_ The array in which to search for the item. 11 | * @param item The item to find the index of. 12 | * @return The index of the item in the array, or type(uint256).max if the item is not found. 13 | */ 14 | function getIndexOfItem(uint256[] storage array_, uint256 item) internal view returns (uint256) { 15 | for (uint256 index = 0; index < array_.length; index++) { 16 | if (array_[index] == item) return index; 17 | } 18 | return type(uint256).max; 19 | } 20 | 21 | /** 22 | * @notice Removes an item from an array. 23 | * @param array_ The array from which to remove the item. 24 | * @param item The item to remove from the array. 25 | */ 26 | function removeFromArray(uint256[] storage array_, uint256 item) internal { 27 | uint256 index = getIndexOfItem(array_, item); 28 | require(index != type(uint256).max, "LibQuote: Item not Found"); 29 | array_[index] = array_[array_.length - 1]; 30 | array_.pop(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /contracts/libraries/SharedEvents.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity >=0.8.18; 6 | 7 | library SharedEvents { 8 | enum BalanceChangeType { 9 | ALLOCATE, 10 | DEALLOCATE, 11 | PLATFORM_FEE_IN, 12 | PLATFORM_FEE_OUT, 13 | REALIZED_PNL_IN, 14 | REALIZED_PNL_OUT, 15 | CVA_IN, 16 | CVA_OUT, 17 | LF_IN, 18 | LF_OUT, 19 | FUNDING_FEE_IN, 20 | FUNDING_FEE_OUT 21 | } 22 | 23 | event BalanceChangePartyA(address indexed partyA, uint256 amount, BalanceChangeType _type); 24 | 25 | event BalanceChangePartyB(address indexed partyB, address indexed partyA, uint256 amount, BalanceChangeType _type); 26 | } 27 | -------------------------------------------------------------------------------- /contracts/libraries/muon/LibMuon.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity >=0.8.18; 6 | 7 | import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; 8 | import "../LibMuonV04ClientBase.sol"; 9 | import "../../storages/AccountStorage.sol"; 10 | 11 | library LibMuon { 12 | using ECDSA for bytes32; 13 | 14 | function getChainId() internal view returns (uint256 id) { 15 | assembly { 16 | id := chainid() 17 | } 18 | } 19 | 20 | // CONTEXT for commented out lines 21 | // We're utilizing muon signatures for asset pricing and user uPNLs calculations. 22 | // Even though these signatures are necessary for full testing of the system, particularly when invoking various methods. 23 | // The process of creating automated functional signature for tests has proven to be either impractical or excessively time-consuming. therefore, we've established commenting out the necessary code as a workaround specifically for testing. 24 | // Essentially, during testing, we temporarily disable the code sections responsible for validating these signatures. The sections I'm referring to are located within the LibMuon file. Specifically, the body of the 'verifyTSSAndGateway' method is a prime candidate for temporary disablement. In addition, several 'require' statements within other functions of this file, which examine the signatures' expiration status, also need to be temporarily disabled. 25 | // However, it is crucial to note that these lines should not be disabled in the production deployed version. 26 | // We emphasize this because they are only disabled for testing purposes. 27 | function verifyTSSAndGateway(bytes32 hash, SchnorrSign memory sign, bytes memory gatewaySignature) internal view { 28 | // == SignatureCheck( == 29 | bool verified = LibMuonV04ClientBase.muonVerify(uint256(hash), sign, MuonStorage.layout().muonPublicKey); 30 | require(verified, "LibMuon: TSS not verified"); 31 | 32 | hash = hash.toEthSignedMessageHash(); 33 | address gatewaySignatureSigner = hash.recover(gatewaySignature); 34 | 35 | require(gatewaySignatureSigner == MuonStorage.layout().validGateway, "LibMuon: Gateway is not valid"); 36 | // == ) == 37 | } 38 | 39 | // Used in PartyB/Account/Liquidation 40 | function verifyPartyBUpnl(SingleUpnlSig memory upnlSig, address partyB, address partyA) internal view { 41 | MuonStorage.Layout storage muonLayout = MuonStorage.layout(); 42 | // == SignatureCheck( == 43 | require(block.timestamp <= upnlSig.timestamp + muonLayout.upnlValidTime, "LibMuon: Expired signature"); 44 | // == ) == 45 | bytes32 hash = keccak256( 46 | abi.encodePacked( 47 | muonLayout.muonAppId, 48 | upnlSig.reqId, 49 | address(this), 50 | partyB, 51 | partyA, 52 | AccountStorage.layout().partyBNonces[partyB][partyA], 53 | upnlSig.upnl, 54 | upnlSig.timestamp, 55 | getChainId() 56 | ) 57 | ); 58 | verifyTSSAndGateway(hash, upnlSig.sigs, upnlSig.gatewaySignature); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /contracts/libraries/muon/LibMuonAccount.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity >=0.8.18; 6 | 7 | import "../../storages/MuonStorage.sol"; 8 | import "../../storages/AccountStorage.sol"; 9 | import "./LibMuon.sol"; 10 | 11 | library LibMuonAccount { 12 | function verifyPartyAUpnl(SingleUpnlSig memory upnlSig, address partyA) internal view { 13 | MuonStorage.Layout storage muonLayout = MuonStorage.layout(); 14 | // == SignatureCheck( == 15 | require(block.timestamp <= upnlSig.timestamp + muonLayout.upnlValidTime, "LibMuon: Expired signature"); 16 | // == ) == 17 | bytes32 hash = keccak256( 18 | abi.encodePacked( 19 | muonLayout.muonAppId, 20 | upnlSig.reqId, 21 | address(this), 22 | partyA, 23 | AccountStorage.layout().partyANonces[partyA], 24 | upnlSig.upnl, 25 | upnlSig.timestamp, 26 | LibMuon.getChainId() 27 | ) 28 | ); 29 | LibMuon.verifyTSSAndGateway(hash, upnlSig.sigs, upnlSig.gatewaySignature); 30 | } 31 | 32 | function verifyPartyBUpnl(SingleUpnlSig memory upnlSig, address partyB, address partyA) internal view { 33 | LibMuon.verifyPartyBUpnl(upnlSig, partyB, partyA); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /contracts/libraries/muon/LibMuonForceActions.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity >=0.8.18; 6 | 7 | import "../../storages/MuonStorage.sol"; 8 | import "../../storages/AccountStorage.sol"; 9 | import "./LibMuon.sol"; 10 | 11 | library LibMuonForceActions { 12 | function verifyHighLowPrice(HighLowPriceSig memory sig, address partyB, address partyA, uint256 symbolId) internal view { 13 | MuonStorage.Layout storage muonLayout = MuonStorage.layout(); 14 | // == SignatureCheck( == 15 | require(block.timestamp <= sig.timestamp + muonLayout.upnlValidTime, "LibMuon: Expired signature"); 16 | // == ) == 17 | bytes32 hash = keccak256( 18 | abi.encodePacked( 19 | muonLayout.muonAppId, 20 | sig.reqId, 21 | address(this), 22 | partyB, 23 | partyA, 24 | AccountStorage.layout().partyBNonces[partyB][partyA], 25 | AccountStorage.layout().partyANonces[partyA], 26 | sig.upnlPartyB, 27 | sig.upnlPartyA, 28 | symbolId, 29 | sig.currentPrice, 30 | sig.startTime, 31 | sig.endTime, 32 | sig.lowest, 33 | sig.highest, 34 | sig.averagePrice, 35 | sig.timestamp, 36 | LibMuon.getChainId() 37 | ) 38 | ); 39 | LibMuon.verifyTSSAndGateway(hash, sig.sigs, sig.gatewaySignature); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /contracts/libraries/muon/LibMuonFundingRate.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity >=0.8.18; 6 | 7 | import "../../storages/MuonStorage.sol"; 8 | import "../../storages/AccountStorage.sol"; 9 | import "./LibMuon.sol"; 10 | 11 | library LibMuonFundingRate { 12 | function verifyPairUpnl(PairUpnlSig memory upnlSig, address partyB, address partyA) internal view { 13 | MuonStorage.Layout storage muonLayout = MuonStorage.layout(); 14 | // == SignatureCheck( == 15 | require(block.timestamp <= upnlSig.timestamp + muonLayout.upnlValidTime, "LibMuon: Expired signature"); 16 | // == ) == 17 | bytes32 hash = keccak256( 18 | abi.encodePacked( 19 | muonLayout.muonAppId, 20 | upnlSig.reqId, 21 | address(this), 22 | partyB, 23 | partyA, 24 | AccountStorage.layout().partyBNonces[partyB][partyA], 25 | AccountStorage.layout().partyANonces[partyA], 26 | upnlSig.upnlPartyB, 27 | upnlSig.upnlPartyA, 28 | upnlSig.timestamp, 29 | LibMuon.getChainId() 30 | ) 31 | ); 32 | LibMuon.verifyTSSAndGateway(hash, upnlSig.sigs, upnlSig.gatewaySignature); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /contracts/libraries/muon/LibMuonLiquidation.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity >=0.8.18; 6 | 7 | import "../../storages/MuonStorage.sol"; 8 | import "../../storages/AccountStorage.sol"; 9 | import "./LibMuon.sol"; 10 | 11 | library LibMuonLiquidation { 12 | function verifyPartyBUpnl(SingleUpnlSig memory upnlSig, address partyB, address partyA) internal view { 13 | LibMuon.verifyPartyBUpnl(upnlSig, partyB, partyA); 14 | } 15 | 16 | function verifyLiquidationSig(LiquidationSig memory liquidationSig, address partyA) internal view { 17 | MuonStorage.Layout storage muonLayout = MuonStorage.layout(); 18 | require(liquidationSig.prices.length == liquidationSig.symbolIds.length, "LibMuon: Invalid length"); 19 | bytes32 hash = keccak256( 20 | abi.encodePacked( 21 | muonLayout.muonAppId, 22 | liquidationSig.reqId, 23 | liquidationSig.liquidationId, 24 | address(this), 25 | "verifyLiquidationSig", 26 | partyA, 27 | AccountStorage.layout().partyANonces[partyA], 28 | liquidationSig.upnl, 29 | liquidationSig.totalUnrealizedLoss, 30 | liquidationSig.symbolIds, 31 | liquidationSig.prices, 32 | liquidationSig.timestamp, 33 | LibMuon.getChainId() 34 | ) 35 | ); 36 | LibMuon.verifyTSSAndGateway(hash, liquidationSig.sigs, liquidationSig.gatewaySignature); 37 | } 38 | 39 | function verifyDeferredLiquidationSig(DeferredLiquidationSig memory liquidationSig, address partyA) internal view { 40 | MuonStorage.Layout storage muonLayout = MuonStorage.layout(); 41 | require(liquidationSig.prices.length == liquidationSig.symbolIds.length, "LibMuon: Invalid length"); 42 | bytes32 hash = keccak256( 43 | abi.encodePacked( 44 | muonLayout.muonAppId, 45 | liquidationSig.reqId, 46 | liquidationSig.liquidationId, 47 | address(this), 48 | "verifyDeferredLiquidationSig", 49 | partyA, 50 | AccountStorage.layout().partyANonces[partyA], 51 | liquidationSig.upnl, 52 | liquidationSig.totalUnrealizedLoss, 53 | liquidationSig.symbolIds, 54 | liquidationSig.prices, 55 | liquidationSig.timestamp, 56 | liquidationSig.liquidationBlockNumber, 57 | liquidationSig.liquidationTimestamp, 58 | liquidationSig.liquidationAllocatedBalance, 59 | LibMuon.getChainId() 60 | ) 61 | ); 62 | LibMuon.verifyTSSAndGateway(hash, liquidationSig.sigs, liquidationSig.gatewaySignature); 63 | } 64 | 65 | function verifyQuotePrices(QuotePriceSig memory priceSig) internal view { 66 | MuonStorage.Layout storage muonLayout = MuonStorage.layout(); 67 | require(priceSig.prices.length == priceSig.quoteIds.length, "LibMuon: Invalid length"); 68 | bytes32 hash = keccak256( 69 | abi.encodePacked( 70 | muonLayout.muonAppId, 71 | priceSig.reqId, 72 | address(this), 73 | priceSig.quoteIds, 74 | priceSig.prices, 75 | priceSig.timestamp, 76 | LibMuon.getChainId() 77 | ) 78 | ); 79 | LibMuon.verifyTSSAndGateway(hash, priceSig.sigs, priceSig.gatewaySignature); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /contracts/libraries/muon/LibMuonPartyA.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity >=0.8.18; 6 | 7 | import "../../storages/MuonStorage.sol"; 8 | import "../../storages/AccountStorage.sol"; 9 | import "./LibMuon.sol"; 10 | 11 | library LibMuonPartyA { 12 | function verifyPartyAUpnlAndPrice(SingleUpnlAndPriceSig memory upnlSig, address partyA, uint256 symbolId) internal view { 13 | MuonStorage.Layout storage muonLayout = MuonStorage.layout(); 14 | // == SignatureCheck( == 15 | require(block.timestamp <= upnlSig.timestamp + muonLayout.upnlValidTime, "LibMuon: Expired signature"); 16 | // == ) == 17 | bytes32 hash = keccak256( 18 | abi.encodePacked( 19 | muonLayout.muonAppId, 20 | upnlSig.reqId, 21 | address(this), 22 | partyA, 23 | AccountStorage.layout().partyANonces[partyA], 24 | upnlSig.upnl, 25 | symbolId, 26 | upnlSig.price, 27 | upnlSig.timestamp, 28 | LibMuon.getChainId() 29 | ) 30 | ); 31 | LibMuon.verifyTSSAndGateway(hash, upnlSig.sigs, upnlSig.gatewaySignature); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /contracts/libraries/muon/LibMuonPartyB.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity >=0.8.18; 6 | 7 | import "./LibMuon.sol"; 8 | 9 | library LibMuonPartyB { 10 | function verifyPairUpnlAndPrice(PairUpnlAndPriceSig memory upnlSig, address partyB, address partyA, uint256 symbolId) internal view { 11 | MuonStorage.Layout storage muonLayout = MuonStorage.layout(); 12 | // == SignatureCheck( == 13 | require(block.timestamp <= upnlSig.timestamp + muonLayout.upnlValidTime, "LibMuon: Expired signature"); 14 | // == ) == 15 | bytes32 hash = keccak256( 16 | abi.encodePacked( 17 | muonLayout.muonAppId, 18 | upnlSig.reqId, 19 | address(this), 20 | partyB, 21 | partyA, 22 | AccountStorage.layout().partyBNonces[partyB][partyA], 23 | AccountStorage.layout().partyANonces[partyA], 24 | upnlSig.upnlPartyB, 25 | upnlSig.upnlPartyA, 26 | symbolId, 27 | upnlSig.price, 28 | upnlSig.timestamp, 29 | LibMuon.getChainId() 30 | ) 31 | ); 32 | LibMuon.verifyTSSAndGateway(hash, upnlSig.sigs, upnlSig.gatewaySignature); 33 | } 34 | 35 | function verifyPartyBUpnl(SingleUpnlSig memory upnlSig, address partyB, address partyA) internal view { 36 | LibMuon.verifyPartyBUpnl(upnlSig, partyB, partyA); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /contracts/libraries/muon/LibMuonSettlement.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity >=0.8.18; 6 | 7 | import "../../storages/MuonStorage.sol"; 8 | import "../../storages/AccountStorage.sol"; 9 | import "./LibMuon.sol"; 10 | 11 | library LibMuonSettlement { 12 | function verifySettlement(SettlementSig memory settleSig, address partyA) internal view { 13 | MuonStorage.Layout storage muonLayout = MuonStorage.layout(); 14 | // == SignatureCheck( == 15 | require(block.timestamp <= settleSig.timestamp + muonLayout.upnlValidTime, "LibMuon: Expired signature"); 16 | // == ) == 17 | bytes memory encodedData; 18 | uint256[] memory nonces = new uint256[](settleSig.quotesSettlementsData.length); 19 | for (uint8 i = 0; i < settleSig.quotesSettlementsData.length; i++) { 20 | nonces[i] = AccountStorage.layout().partyBNonces[QuoteStorage.layout().quotes[settleSig.quotesSettlementsData[i].quoteId].partyB][partyA]; 21 | encodedData = abi.encodePacked( 22 | encodedData, // Append the previously encoded data 23 | settleSig.quotesSettlementsData[i].quoteId, 24 | settleSig.quotesSettlementsData[i].currentPrice, 25 | settleSig.quotesSettlementsData[i].partyBUpnlIndex 26 | ); 27 | } 28 | bytes32 hash = keccak256( 29 | abi.encodePacked( 30 | muonLayout.muonAppId, 31 | settleSig.reqId, 32 | address(this), 33 | "verifySettlement", 34 | nonces, 35 | AccountStorage.layout().partyANonces[partyA], 36 | encodedData, 37 | settleSig.upnlPartyBs, 38 | settleSig.upnlPartyA, 39 | settleSig.timestamp, 40 | LibMuon.getChainId() 41 | ) 42 | ); 43 | LibMuon.verifyTSSAndGateway(hash, settleSig.sigs, settleSig.gatewaySignature); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /contracts/multiAccount/SymmioPartyA.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity >=0.8.18; 6 | 7 | import "@openzeppelin/contracts/access/AccessControl.sol"; 8 | 9 | contract SymmioPartyA is AccessControl { 10 | bytes32 public constant MULTIACCOUNT_ROLE = keccak256("MULTIACCOUNT_ROLE"); 11 | address public symmioAddress; 12 | 13 | /** 14 | * @dev Constructor to initialize the contract with roles and Symmio address. 15 | * @param admin The address of the default admin role. 16 | * @param multiAccountAddress The address assigned the MULTIACCOUNT_ROLE. 17 | * @param symmioAddress_ The address of the Symmio contract. 18 | */ 19 | constructor(address admin, address multiAccountAddress, address symmioAddress_) { 20 | _grantRole(DEFAULT_ADMIN_ROLE, admin); 21 | _grantRole(MULTIACCOUNT_ROLE, multiAccountAddress); 22 | symmioAddress = symmioAddress_; 23 | } 24 | 25 | /** 26 | * @dev Emitted when the Symmio address is updated. 27 | * @param oldSymmioContractAddress The address of the old Symmio contract. 28 | * @param newSymmioContractAddress The address of the new Symmio contract. 29 | */ 30 | event SetSymmioAddress(address oldSymmioContractAddress, address newSymmioContractAddress); 31 | 32 | /** 33 | * @dev Updates the address of the Symmio contract. 34 | * @param symmioAddress_ The new address of the Symmio contract. 35 | */ 36 | function setSymmioAddress(address symmioAddress_) external onlyRole(DEFAULT_ADMIN_ROLE) { 37 | emit SetSymmioAddress(symmioAddress, symmioAddress_); 38 | symmioAddress = symmioAddress_; 39 | } 40 | 41 | /** 42 | * @dev Executes a function call on the Symmio contract. 43 | * @param _callData The data to be used for the function call. 44 | * @return _success A boolean indicating whether the call was successful. 45 | * @return _resultData The result data returned by the function call. 46 | */ 47 | function _call(bytes memory _callData) external onlyRole(MULTIACCOUNT_ROLE) returns (bool _success, bytes memory _resultData) { 48 | return symmioAddress.call{ value: 0 }(_callData); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /contracts/storages/AccountStorage.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity >=0.8.18; 6 | 7 | import "../libraries/LibLockedValues.sol"; 8 | 9 | enum LiquidationType { 10 | NONE, 11 | NORMAL, 12 | LATE, 13 | OVERDUE 14 | } 15 | 16 | struct SettlementState { 17 | int256 actualAmount; 18 | int256 expectedAmount; 19 | uint256 cva; 20 | bool pending; 21 | } 22 | 23 | struct LiquidationDetail { 24 | bytes liquidationId; 25 | LiquidationType liquidationType; 26 | int256 upnl; 27 | int256 totalUnrealizedLoss; 28 | uint256 deficit; 29 | uint256 liquidationFee; 30 | uint256 timestamp; 31 | uint256 involvedPartyBCounts; 32 | int256 partyAAccumulatedUpnl; 33 | bool disputed; 34 | uint256 liquidationTimestamp; 35 | } 36 | 37 | struct Price { 38 | uint256 price; 39 | uint256 timestamp; 40 | } 41 | 42 | library AccountStorage { 43 | bytes32 internal constant ACCOUNT_STORAGE_SLOT = keccak256("diamond.standard.storage.account"); 44 | 45 | struct Layout { 46 | // Users deposited amounts 47 | mapping(address => uint256) balances; 48 | mapping(address => uint256) allocatedBalances; 49 | // position value will become pending locked before openPosition and will be locked after that 50 | mapping(address => LockedValues) pendingLockedBalances; 51 | mapping(address => LockedValues) lockedBalances; 52 | mapping(address => mapping(address => uint256)) partyBAllocatedBalances; 53 | mapping(address => mapping(address => LockedValues)) partyBPendingLockedBalances; 54 | mapping(address => mapping(address => LockedValues)) partyBLockedBalances; 55 | mapping(address => uint256) withdrawCooldown; // is better to call lastDeallocateTime 56 | mapping(address => uint256) partyANonces; 57 | mapping(address => mapping(address => uint256)) partyBNonces; 58 | mapping(address => bool) suspendedAddresses; 59 | mapping(address => LiquidationDetail) liquidationDetails; 60 | mapping(address => mapping(uint256 => Price)) symbolsPrices; 61 | mapping(address => address[]) liquidators; 62 | mapping(address => uint256) partyAReimbursement; 63 | // partyA => partyB => SettlementState 64 | mapping(address => mapping(address => SettlementState)) settlementStates; 65 | mapping(address => uint256) reserveVault; 66 | } 67 | 68 | function layout() internal pure returns (Layout storage l) { 69 | bytes32 slot = ACCOUNT_STORAGE_SLOT; 70 | assembly { 71 | l.slot := slot 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /contracts/storages/BridgeStorage.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity >=0.8.18; 6 | 7 | struct BridgeTransaction { 8 | uint256 id; 9 | uint256 amount; 10 | address user; 11 | address bridge; 12 | uint256 timestamp; 13 | BridgeTransactionStatus status; 14 | } 15 | 16 | enum BridgeTransactionStatus { 17 | RECEIVED, 18 | SUSPENDED, 19 | WITHDRAWN 20 | } 21 | 22 | library BridgeStorage { 23 | bytes32 internal constant BRIDGE_STORAGE_SLOT = keccak256("diamond.standard.storage.bridge"); 24 | 25 | struct Layout { 26 | mapping(address => bool) bridges; 27 | mapping(uint256 => BridgeTransaction) bridgeTransactions; 28 | mapping(address => uint256[]) bridgeTransactionIds; 29 | uint256 lastId; 30 | address invalidBridgedAmountsPool; 31 | } 32 | 33 | function layout() internal pure returns (Layout storage l) { 34 | bytes32 slot = BRIDGE_STORAGE_SLOT; 35 | assembly { 36 | l.slot := slot 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /contracts/storages/GlobalAppStorage.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity >=0.8.18; 6 | 7 | import "../libraries/LibLockedValues.sol"; 8 | 9 | library GlobalAppStorage { 10 | bytes32 internal constant GLOBAL_APP_STORAGE_SLOT = keccak256("diamond.standard.storage.global"); 11 | 12 | struct Layout { 13 | address collateral; 14 | address defaultFeeCollector; 15 | bool globalPaused; 16 | bool liquidationPaused; 17 | bool accountingPaused; 18 | bool partyBActionsPaused; 19 | bool partyAActionsPaused; 20 | bool emergencyMode; 21 | uint256 balanceLimitPerUser; 22 | mapping(address => bool) partyBEmergencyStatus; 23 | mapping(address => mapping(bytes32 => bool)) hasRole; 24 | bool internalTransferPaused; 25 | mapping(address => address) affiliateFeeCollector; 26 | } 27 | 28 | function layout() internal pure returns (Layout storage l) { 29 | bytes32 slot = GLOBAL_APP_STORAGE_SLOT; 30 | assembly { 31 | l.slot := slot 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /contracts/storages/MAStorage.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity >=0.8.18; 6 | 7 | import "../libraries/LibLockedValues.sol"; 8 | 9 | library MAStorage { 10 | bytes32 internal constant MA_STORAGE_SLOT = keccak256("diamond.standard.storage.masteragreement"); 11 | 12 | struct Layout { 13 | uint256 deallocateCooldown; 14 | uint256 forceCancelCooldown; 15 | uint256 forceCancelCloseCooldown; 16 | uint256 forceCloseFirstCooldown; 17 | uint256 liquidationTimeout; 18 | uint256 liquidatorShare; // in 18 decimals 19 | uint256 pendingQuotesValidLength; 20 | uint256 deprecatedForceCloseGapRatio; // DEPRECATED 21 | mapping(address => bool) partyBStatus; 22 | mapping(address => bool) liquidationStatus; 23 | mapping(address => mapping(address => bool)) partyBLiquidationStatus; 24 | mapping(address => mapping(address => uint256)) partyBLiquidationTimestamp; 25 | mapping(address => mapping(address => uint256)) partyBPositionLiquidatorsShare; 26 | address[] partyBList; 27 | uint256 forceCloseSecondCooldown; 28 | uint256 forceClosePricePenalty; 29 | uint256 forceCloseMinSigPeriod; 30 | uint256 deallocateDebounceTime; 31 | mapping(address => bool) affiliateStatus; 32 | uint256 settlementCooldown; 33 | mapping(address => mapping(address => mapping(address => uint256))) lastUpnlSettlementTimestamp; // subject partyB => object partyB => partyA => timestamp 34 | } 35 | 36 | function layout() internal pure returns (Layout storage l) { 37 | bytes32 slot = MA_STORAGE_SLOT; 38 | assembly { 39 | l.slot := slot 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /contracts/storages/QuoteStorage.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity >=0.8.18; 6 | 7 | enum PositionType { 8 | LONG, 9 | SHORT 10 | } 11 | 12 | enum OrderType { 13 | LIMIT, 14 | MARKET 15 | } 16 | 17 | enum QuoteStatus { 18 | PENDING, //0 19 | LOCKED, //1 20 | CANCEL_PENDING, //2 21 | CANCELED, //3 22 | OPENED, //4 23 | CLOSE_PENDING, //5 24 | CANCEL_CLOSE_PENDING, //6 25 | CLOSED, //7 26 | LIQUIDATED, //8 27 | EXPIRED, //9 28 | LIQUIDATED_PENDING //10 29 | } 30 | 31 | struct LockedValues { 32 | uint256 cva; 33 | uint256 lf; 34 | uint256 partyAmm; 35 | uint256 partyBmm; 36 | } 37 | 38 | struct Quote { 39 | uint256 id; 40 | address[] partyBsWhiteList; 41 | uint256 symbolId; 42 | PositionType positionType; 43 | OrderType orderType; 44 | // Price of quote which PartyB opened in 18 decimals 45 | uint256 openedPrice; 46 | uint256 initialOpenedPrice; 47 | // Price of quote which PartyA requested in 18 decimals 48 | uint256 requestedOpenPrice; 49 | uint256 marketPrice; 50 | // Quantity of quote which PartyA requested in 18 decimals 51 | uint256 quantity; 52 | // Quantity of quote which PartyB has closed until now in 18 decimals 53 | uint256 closedAmount; 54 | LockedValues initialLockedValues; 55 | LockedValues lockedValues; 56 | uint256 maxFundingRate; 57 | address partyA; 58 | address partyB; 59 | QuoteStatus quoteStatus; 60 | uint256 avgClosedPrice; 61 | uint256 requestedClosePrice; 62 | uint256 quantityToClose; 63 | // handle partially open position 64 | uint256 parentId; 65 | uint256 createTimestamp; 66 | uint256 statusModifyTimestamp; 67 | uint256 lastFundingPaymentTimestamp; 68 | uint256 deadline; 69 | uint256 tradingFee; 70 | address affiliate; 71 | } 72 | 73 | library QuoteStorage { 74 | bytes32 internal constant QUOTE_STORAGE_SLOT = keccak256("diamond.standard.storage.quote"); 75 | 76 | struct Layout { 77 | mapping(address => uint256[]) quoteIdsOf; 78 | mapping(uint256 => Quote) quotes; 79 | mapping(address => uint256) partyAPositionsCount; 80 | mapping(address => mapping(address => uint256)) partyBPositionsCount; 81 | mapping(address => uint256[]) partyAPendingQuotes; 82 | mapping(address => mapping(address => uint256[])) partyBPendingQuotes; 83 | mapping(address => uint256[]) partyAOpenPositions; 84 | mapping(uint256 => uint256) partyAPositionsIndex; 85 | mapping(address => mapping(address => uint256[])) partyBOpenPositions; 86 | mapping(uint256 => uint256) partyBPositionsIndex; 87 | uint256 lastId; 88 | uint256 lastCloseId; 89 | mapping(uint256 => uint256) closeIds; 90 | } 91 | 92 | function layout() internal pure returns (Layout storage l) { 93 | bytes32 slot = QUOTE_STORAGE_SLOT; 94 | assembly { 95 | l.slot := slot 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /contracts/storages/SymbolStorage.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity >=0.8.18; 6 | 7 | struct Symbol { 8 | uint256 symbolId; 9 | string name; 10 | bool isValid; 11 | uint256 minAcceptableQuoteValue; 12 | uint256 minAcceptablePortionLF; 13 | uint256 tradingFee; 14 | uint256 maxLeverage; 15 | uint256 fundingRateEpochDuration; 16 | uint256 fundingRateWindowTime; 17 | } 18 | 19 | library SymbolStorage { 20 | bytes32 internal constant SYMBOL_STORAGE_SLOT = keccak256("diamond.standard.storage.symbol"); 21 | 22 | struct Layout { 23 | mapping(uint256 => Symbol) symbols; 24 | uint256 lastId; 25 | mapping(uint256 => uint256) forceCloseGapRatio; // symbolId -> forceCloseGapRatio 26 | } 27 | 28 | function layout() internal pure returns (Layout storage l) { 29 | bytes32 slot = SYMBOL_STORAGE_SLOT; 30 | assembly { 31 | l.slot := slot 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /contracts/test/MockSymmio.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.18; 3 | 4 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 5 | 6 | contract MockSymmio { 7 | address public collateral; 8 | mapping(address => uint256) public balances; 9 | 10 | function setCollateral(address _collateral) external { 11 | collateral = _collateral; 12 | } 13 | 14 | function withdraw(uint256 amount) external { 15 | require(balances[msg.sender] >= amount, "Insufficient balance"); 16 | balances[msg.sender] -= amount; 17 | IERC20(collateral).transfer(msg.sender, amount); 18 | } 19 | 20 | function getCollateral() external view returns (address) { 21 | return collateral; 22 | } 23 | 24 | // Function to simulate depositing collateral 25 | function depositFor(uint256 amount, address user) external { 26 | IERC20(collateral).transferFrom(msg.sender, address(this), amount); 27 | balances[user] += amount; 28 | } 29 | 30 | function balanceOf(address user) external view returns (uint256) { 31 | return balances[user]; 32 | } 33 | } -------------------------------------------------------------------------------- /contracts/test/MockToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.18; 3 | 4 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 5 | 6 | contract MockToken is ERC20 { 7 | constructor(string memory name, string memory symbol) ERC20(name, symbol) { 8 | _mint(msg.sender, 1000000 * 10 ** decimals()); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /contracts/upgradeInitializers/DiamondInit.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.18; 3 | 4 | /******************************************************************************\ 5 | * Author: Nick Mudge (https://twitter.com/mudgen) 6 | * EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535 7 | * 8 | * Implementation of a diamond. 9 | /******************************************************************************/ 10 | 11 | import { LibDiamond } from "../libraries/LibDiamond.sol"; 12 | import { IDiamondLoupe } from "../facets/DiamondLoup/IDiamondLoupe.sol"; 13 | import { IDiamondCut } from "../facets/DiamondCut/IDiamondCut.sol"; 14 | import { IERC165 } from "../interfaces/IERC165.sol"; 15 | 16 | contract DiamondInit { 17 | function init() external { 18 | LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); 19 | ds.supportedInterfaces[type(IERC165).interfaceId] = true; 20 | ds.supportedInterfaces[type(IDiamondCut).interfaceId] = true; 21 | ds.supportedInterfaces[type(IDiamondLoupe).interfaceId] = true; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /contracts/utils/Accessibility.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity >=0.8.18; 6 | 7 | import "../storages/MAStorage.sol"; 8 | import "../storages/AccountStorage.sol"; 9 | import "../storages/QuoteStorage.sol"; 10 | import "../libraries/LibAccessibility.sol"; 11 | 12 | abstract contract Accessibility { 13 | modifier onlyPartyB() { 14 | require(MAStorage.layout().partyBStatus[msg.sender], "Accessibility: Should be partyB"); 15 | _; 16 | } 17 | 18 | modifier notPartyB() { 19 | require(!MAStorage.layout().partyBStatus[msg.sender], "Accessibility: Shouldn't be partyB"); 20 | _; 21 | } 22 | 23 | modifier userNotPartyB(address user) { 24 | require(!MAStorage.layout().partyBStatus[user], "Accessibility: Shouldn't be partyB"); 25 | _; 26 | } 27 | 28 | modifier onlyRole(bytes32 role) { 29 | require(LibAccessibility.hasRole(msg.sender, role), "Accessibility: Must has role"); 30 | _; 31 | } 32 | 33 | modifier notLiquidatedPartyA(address partyA) { 34 | require(!MAStorage.layout().liquidationStatus[partyA], "Accessibility: PartyA isn't solvent"); 35 | _; 36 | } 37 | 38 | modifier notLiquidatedPartyB(address partyB, address partyA) { 39 | require(!MAStorage.layout().partyBLiquidationStatus[partyB][partyA], "Accessibility: PartyB isn't solvent"); 40 | _; 41 | } 42 | 43 | modifier notLiquidated(uint256 quoteId) { 44 | Quote storage quote = QuoteStorage.layout().quotes[quoteId]; 45 | require(!MAStorage.layout().liquidationStatus[quote.partyA], "Accessibility: PartyA isn't solvent"); 46 | require(!MAStorage.layout().partyBLiquidationStatus[quote.partyB][quote.partyA], "Accessibility: PartyB isn't solvent"); 47 | require( 48 | quote.quoteStatus != QuoteStatus.LIQUIDATED && 49 | quote.quoteStatus != QuoteStatus.LIQUIDATED_PENDING && 50 | quote.quoteStatus != QuoteStatus.CLOSED, 51 | "Accessibility: Invalid state" 52 | ); 53 | _; 54 | } 55 | 56 | modifier onlyPartyAOfQuote(uint256 quoteId) { 57 | Quote storage quote = QuoteStorage.layout().quotes[quoteId]; 58 | require(quote.partyA == msg.sender, "Accessibility: Should be partyA of quote"); 59 | _; 60 | } 61 | 62 | modifier onlyPartyBOfQuote(uint256 quoteId) { 63 | Quote storage quote = QuoteStorage.layout().quotes[quoteId]; 64 | require(quote.partyB == msg.sender, "Accessibility: Should be partyB of quote"); 65 | _; 66 | } 67 | 68 | modifier notSuspended(address user) { 69 | require(!AccountStorage.layout().suspendedAddresses[user], "Accessibility: Sender is Suspended"); 70 | _; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /contracts/utils/Ownable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity >=0.8.18; 6 | 7 | import "../libraries/LibDiamond.sol"; 8 | 9 | abstract contract Ownable { 10 | modifier onlyOwner() { 11 | LibDiamond.enforceIsContractOwner(); 12 | _; 13 | } 14 | 15 | modifier onlyOwnerOrContract() { 16 | LibDiamond.enforceIsOwnerOrContract(); 17 | _; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /contracts/utils/Pausable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SYMM-Core-Business-Source-License-1.1 2 | // This contract is licensed under the SYMM Core Business Source License 1.1 3 | // Copyright (c) 2023 Symmetry Labs AG 4 | // For more information, see https://docs.symm.io/legal-disclaimer/license 5 | pragma solidity >=0.8.18; 6 | 7 | import "../storages/GlobalAppStorage.sol"; 8 | 9 | abstract contract Pausable { 10 | modifier whenNotGlobalPaused() { 11 | require(!GlobalAppStorage.layout().globalPaused, "Pausable: Global paused"); 12 | _; 13 | } 14 | 15 | modifier whenNotLiquidationPaused() { 16 | require(!GlobalAppStorage.layout().globalPaused, "Pausable: Global paused"); 17 | require(!GlobalAppStorage.layout().liquidationPaused, "Pausable: Liquidation paused"); 18 | _; 19 | } 20 | 21 | modifier whenNotAccountingPaused() { 22 | require(!GlobalAppStorage.layout().globalPaused, "Pausable: Global paused"); 23 | require(!GlobalAppStorage.layout().accountingPaused, "Pausable: Accounting paused"); 24 | _; 25 | } 26 | 27 | modifier whenNotPartyAActionsPaused() { 28 | require(!GlobalAppStorage.layout().globalPaused, "Pausable: Global paused"); 29 | require(!GlobalAppStorage.layout().partyAActionsPaused, "Pausable: PartyA actions paused"); 30 | _; 31 | } 32 | 33 | modifier whenNotPartyBActionsPaused() { 34 | require(!GlobalAppStorage.layout().globalPaused, "Pausable: Global paused"); 35 | require(!GlobalAppStorage.layout().partyBActionsPaused, "Pausable: PartyB actions paused"); 36 | _; 37 | } 38 | 39 | modifier whenNotInternalTransferPaused() { 40 | require(!GlobalAppStorage.layout().internalTransferPaused, "Pausable: Internal transfer paused"); 41 | require(!GlobalAppStorage.layout().accountingPaused, "Pausable: Accounting paused"); 42 | _; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /deploy-local.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | npx hardhat run scripts/Initialize.ts --network localhost 3 | -------------------------------------------------------------------------------- /docker/compile.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | npx hardhat compile 4 | test -d artifacts || { 5 | echo 'compile failed' 6 | exit 1 7 | } 8 | -------------------------------------------------------------------------------- /docker/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | set -x 3 | 4 | time npx hardhat run scripts/Initialize.ts --network docker 5 | time npx hardhat run scripts/deployPartyB.ts --network docker 6 | time npx hardhat run scripts/deployMultiAccount.ts --network docker 7 | -------------------------------------------------------------------------------- /funding.json: -------------------------------------------------------------------------------- 1 | { 2 | "opRetro": { 3 | "projectId": "0x2ddf5b9dc64f873d6557b4f63de936cf278f8851deda857244a1be44d1a1e950" 4 | } 5 | } -------------------------------------------------------------------------------- /fuzz-tests: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | ./docker/compile.sh 3 | TEST_MODE=fuzz npx hardhat test 4 | -------------------------------------------------------------------------------- /images/liq_example1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SYMM-IO/protocol-core/afc252985c2a57bfd2239d0a530de9811d87d42e/images/liq_example1.png -------------------------------------------------------------------------------- /images/liq_example2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SYMM-IO/protocol-core/afc252985c2a57bfd2239d0a530de9811d87d42e/images/liq_example2.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "symmio", 3 | "description": "Symmio platform contracts", 4 | "version": "0.8.4", 5 | "author": { 6 | "name": "MoonKnight/Naveedinno", 7 | "url": "https://github.com/SYMM-IO/symmio-core" 8 | }, 9 | "devDependencies": { 10 | "@ethersproject/abi": "^5.7.0", 11 | "@ethersproject/abstract-signer": "^5.7.0", 12 | "@ethersproject/bignumber": "^5.7.0", 13 | "@ethersproject/bytes": "^5.7.0", 14 | "@ethersproject/providers": "^5.7.2", 15 | "@nomicfoundation/hardhat-chai-matchers": "^2.0.7", 16 | "@nomicfoundation/hardhat-ethers": "^3.0.6", 17 | "@nomicfoundation/hardhat-network-helpers": "^1.0.11", 18 | "@nomicfoundation/hardhat-toolbox": "^5.0.0", 19 | "@nomicfoundation/hardhat-verify": "^2.0.8", 20 | "@openzeppelin/contracts": "^4.9.6", 21 | "@openzeppelin/contracts-upgradeable": "^4.9.6", 22 | "@openzeppelin/hardhat-upgrades": "^3.0.1", 23 | "@typechain/ethers-v6": "^0.5.1", 24 | "@typechain/hardhat": "^9.1.0", 25 | "@types/chai": "^4.3.16", 26 | "@types/fs-extra": "^11.0.4", 27 | "@types/mocha": "^10.0.7", 28 | "@types/node": "^20.14.12", 29 | "bignumber.js": "^9.1.2", 30 | "builder-pattern": "^2.2.0", 31 | "chai": "^4.2.0", 32 | "cross-env": "^7.0.3", 33 | "dotenv": "^16.4.5", 34 | "ethers": "^6.13.1", 35 | "fs-extra": "^11.2.0", 36 | "hardhat": "^2.22.6", 37 | "hardhat-gas-reporter": "^1.0.8", 38 | "js-sha3": "^0.9.3", 39 | "mocha": "^10.7.0", 40 | "random-ext": "^2.8.0", 41 | "rxjs": "^7.8.1", 42 | "shx": "^0.3.4", 43 | "solidity-coverage": "^0.8.12", 44 | "solidity-docgen": "^0.6.0-beta.36", 45 | "ts-node": "^10.9.2", 46 | "typechain": "^8.3.2", 47 | "typescript": "^5.5.4", 48 | "typescript-json-serializer": "^6.0.1", 49 | "winston": "^3.13.1" 50 | }, 51 | "files": [ 52 | "/contracts" 53 | ], 54 | "keywords": [ 55 | "defi", 56 | "ethereum", 57 | "hardhat", 58 | "smart-contracts", 59 | "solidity", 60 | "typescript" 61 | ], 62 | "packageManager": "yarn@1.22.22", 63 | "publishConfig": { 64 | "access": "public" 65 | }, 66 | "scripts": { 67 | "clean": "shx rm -rf ./artifacts ./cache ./coverage ./src/types ./coverage.json && yarn typechain", 68 | "compile": "cross-env TS_NODE_TRANSPILE_ONLY=true hardhat compile", 69 | "coverage": "hardhat coverage --testfiles \"test/**/*.ts\" && yarn typechain", 70 | "lint": "yarn lint:sol && yarn lint:ts", 71 | "lint:sol": "solhint --config ./.solhint.json --solcoverjs ./.solcover.js --max-warnings 0 \"contracts/**/*.sol\"", 72 | "postinstall": "DOTENV_CONFIG_PATH=./.env yarn typechain", 73 | "test": "hardhat test", 74 | "typechain": "cross-env TS_NODE_TRANSPILE_ONLY=true hardhat typechain" 75 | }, 76 | "dependencies": { 77 | "lodash": "^4.17.21" 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /scripts/callAMethod.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "hardhat" 2 | 3 | async function main() { 4 | let symmioAddress = "" 5 | let ViewFacet = await ethers.getContractAt("ViewFacet", symmioAddress) 6 | console.log(await ViewFacet.getQuote(564)) 7 | } 8 | 9 | // We recommend this pattern to be able to use async/await everywhere 10 | // and properly handle errors. 11 | main().catch(error => { 12 | console.error(error) 13 | process.exitCode = 1 14 | }) 15 | -------------------------------------------------------------------------------- /scripts/config/setup.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "admin": "0xabCDeF0123456789AbcdEf0123456789aBCDEF01", 3 | "grantRoles": [ 4 | { 5 | "roleUser": "0x0123456789abcDEF0123456789abCDef01234567", 6 | "role": "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd" 7 | }, 8 | { 9 | "roleUser": "0x456789AbcDeF0123456789AbCdEF0123456789AB", 10 | "role": "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd" 11 | } 12 | ], 13 | "revokeRoles": [ 14 | { 15 | "roleUser": "0x0123456789abcDEF0123456789abCDef01234567", 16 | "role": "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd" 17 | }, 18 | { 19 | "roleUser": "0x456789AbcDeF0123456789AbCdEF0123456789AB", 20 | "role": "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd" 21 | } 22 | ], 23 | "partyBs": [ 24 | "0x9876543210AbCDef9876543210ABCdEf98765432", 25 | "0xABcdEFABcdEFabcdEfAbCdefabcdeFABcDEFabCD" 26 | ], 27 | "affiliates": [ 28 | { 29 | "affiliate": "0xAbc123AbC123Abc123aBc123abC123ABC123ABc1", 30 | "feeCollector": "0x123aBc123AbC123AbC123ABC123aBC123ABC123a" 31 | }, 32 | { 33 | "affiliate": "0x789Abc789aBc789abc789abC789ABC789abC789A", 34 | "feeCollector": "0x1234567890AbcdEF1234567890aBcdef12345678" 35 | } 36 | ], 37 | "muon": { 38 | "upnlValidTime": 3600, 39 | "priceValidTime": 3600, 40 | "muonAppId": 123, 41 | "validGateway": "0x9876543210AbCDef9876543210ABCdEf98765432", 42 | "publicKey": { 43 | "x": "0x1234567890AbcdEF1234567890aBcdef12345678", 44 | "parity": 2 45 | } 46 | }, 47 | "collateral": "0x5fbdb2315678afecb367f032d93f642f64180aa3", 48 | "pendingQuotesValidLength": 5, 49 | "defaultFeeCollector": "0x789Abc789aBc789abc789abC789ABC789abC789A", 50 | "deallocateDebounceTime": 300, 51 | "invalidBridgedAmountsPool": "0xABcdEFABcdEFabcdEfAbCdefabcdeFABcDEFabCD", 52 | "symbols": [ 53 | { 54 | "symbolId": 1, 55 | "name": "BTC", 56 | "isValid": true, 57 | "minAcceptableQuoteValue": 1000, 58 | "minAcceptablePortionLF": 10, 59 | "tradingFee": 100, 60 | "maxLeverage": 10, 61 | "fundingRateEpochDuration": 86400, 62 | "fundingRateWindowTime": 3600, 63 | "forceCloseGapRatio": 2 64 | }, 65 | { 66 | "symbolId": 2, 67 | "name": "ETH", 68 | "isValid": true, 69 | "minAcceptableQuoteValue": 500, 70 | "minAcceptablePortionLF": 5, 71 | "tradingFee": 50, 72 | "maxLeverage": 5, 73 | "fundingRateEpochDuration": 86400, 74 | "fundingRateWindowTime": 3600, 75 | "forceCloseGapRatio": 3 76 | } 77 | ], 78 | "deallocateCooldown": 3600, 79 | "forceCancelCooldown": 1800, 80 | "forceCloseFirstCooldown": 1200, 81 | "forceCloseSecondCooldown": 600, 82 | "forceClosePricePenalty": 5, 83 | "forceCloseMinSigPeriod": 600, 84 | "forceCancelCloseCooldown": 300, 85 | "liquidatorShare": 20, 86 | "settlementCooldown": 600, 87 | "liquidationTimeout": 300, 88 | "balanceLimitPerUser": 1000000, 89 | "bridges": [ 90 | "0x1234567890AbcdEF1234567890aBcdef12345678", 91 | "0xABcdEFABcdEFabcdEfAbCdefabcdeFABcDEFabCD" 92 | ] 93 | } 94 | -------------------------------------------------------------------------------- /scripts/deploy.ts: -------------------------------------------------------------------------------- 1 | import {ethers, run} from "hardhat" 2 | 3 | 4 | async function main() { 5 | const facetName = "" 6 | const Facet = await ethers.getContractFactory(facetName) 7 | const facet = await Facet.deploy() 8 | 9 | await facet.waitForDeployment() 10 | 11 | console.log(`${facetName} deployed: ${await facet.getAddress()}`) 12 | await run("verify:verify", { 13 | address: await facet.getAddress(), 14 | constructorArguments: [], 15 | }) 16 | } 17 | 18 | // We recommend this pattern to be able to use async/await everywhere 19 | // and properly handle errors. 20 | main().catch(error => { 21 | console.error(error) 22 | process.exitCode = 1 23 | }) 24 | -------------------------------------------------------------------------------- /scripts/deployAll.ts: -------------------------------------------------------------------------------- 1 | import {ethers, run} from "hardhat" 2 | import {sleep} from "@nomicfoundation/hardhat-verify/internal/utilities" 3 | 4 | async function main() { 5 | const facetNames = [ 6 | "AccountFacet", 7 | "ControlFacet", 8 | "DiamondLoupeFacet", 9 | "LiquidationFacet", 10 | "PartyAFacet", 11 | "BridgeFacet", 12 | "ViewFacet", 13 | "FundingRateFacet", 14 | "ForceActionsFacet", 15 | "SettlementFacet", 16 | "PartyBPositionActionsFacet", 17 | "PartyBQuoteActionsFacet", 18 | "PartyBGroupActionsFacet", 19 | ] 20 | for (const facetName of facetNames) { 21 | const Facet = await ethers.getContractFactory(facetName) 22 | const facet = await Facet.deploy() 23 | 24 | await facet.waitForDeployment() 25 | 26 | let addr = await facet.getAddress() 27 | console.log(`${facetName} deployed: ${addr}`) 28 | 29 | await sleep(10000) 30 | 31 | try { 32 | await run("verify:verify", { 33 | address: addr, 34 | constructorArguments: [], 35 | }) 36 | } catch (e) { 37 | console.log("Failed to verify contract", e) 38 | } 39 | } 40 | } 41 | 42 | // We recommend this pattern to be able to use async/await everywhere 43 | // and properly handle errors. 44 | main().catch(error => { 45 | console.error(error) 46 | process.exitCode = 1 47 | }) 48 | -------------------------------------------------------------------------------- /scripts/deployCallProxy.ts: -------------------------------------------------------------------------------- 1 | import { run } from "hardhat" 2 | 3 | async function main() { 4 | const admin = "" 5 | const whitelist = "" 6 | const operatorsList: string[] = [ ] 7 | const operators = operatorsList.join(",") 8 | 9 | // Run the deploy:callProxy task 10 | const contract = await run("deploy:callProxy", { 11 | admin, 12 | whitelist, 13 | operators, 14 | }) 15 | } 16 | 17 | main() 18 | .then(() => process.exit(0)) 19 | .catch(error => { 20 | console.error(error) 21 | process.exit(1) 22 | }) 23 | -------------------------------------------------------------------------------- /scripts/deployFeeDistributor.ts: -------------------------------------------------------------------------------- 1 | import {run} from "hardhat" 2 | import {Addresses, loadAddresses} from "./utils/file" 3 | 4 | async function main() { 5 | let deployedAddresses: Addresses = loadAddresses() 6 | const symmioAddress = deployedAddresses.symmioAddress 7 | const admin = process.env.ADMIN_PUBLIC_KEY 8 | const symmioShare = "" 9 | const symmioShareReceiver = "" 10 | 11 | // Run the deploy:feeDistributor task 12 | const contract = await run("deploy:feeDistributor", { 13 | symmioAddress, 14 | admin, 15 | symmioShare, 16 | symmioShareReceiver 17 | }) 18 | } 19 | 20 | main() 21 | .then(() => process.exit(0)) 22 | .catch(error => { 23 | console.error(error) 24 | process.exit(1) 25 | }) 26 | -------------------------------------------------------------------------------- /scripts/deployMultiAccount.ts: -------------------------------------------------------------------------------- 1 | import { run, ethers } from "hardhat"; 2 | import { Addresses, loadAddresses, saveAddresses } from "./utils/file"; 3 | 4 | async function main() { 5 | let deployedAddresses: Addresses = loadAddresses(); 6 | const symmioAddress = deployedAddresses.symmioAddress; 7 | const admin = process.env.ADMIN_PUBLIC_KEY; 8 | 9 | // Run the deploy:multiAccount task 10 | const contract = await run("deploy:multiAccount", { 11 | symmioAddress, 12 | admin, 13 | logData: true, 14 | }); 15 | 16 | deployedAddresses.multiAccountAddress = await contract.getAddress(); 17 | saveAddresses(deployedAddresses); 18 | } 19 | 20 | main() 21 | .then(() => process.exit(0)) 22 | .catch(error => { 23 | console.error(error); 24 | process.exit(1); 25 | }); 26 | -------------------------------------------------------------------------------- /scripts/deployPartyB.ts: -------------------------------------------------------------------------------- 1 | import {run} from "hardhat" 2 | import {Addresses, loadAddresses, saveAddresses} from "./utils/file" 3 | 4 | async function main() { 5 | let deployedAddresses: Addresses = loadAddresses() 6 | const symmioAddress = deployedAddresses.symmioAddress 7 | const admin = process.env.ADMIN_PUBLIC_KEY 8 | 9 | // Run the deploy:symmioPartyB task 10 | const contract = await run("deploy:symmioPartyB", { 11 | symmioAddress: symmioAddress, 12 | admin: admin, 13 | logData: true, 14 | }) 15 | 16 | deployedAddresses.hedgerProxyAddress = await contract.getAddress() 17 | saveAddresses(deployedAddresses) 18 | } 19 | 20 | main() 21 | .then(() => process.exit(0)) 22 | .catch(error => { 23 | console.error(error) 24 | process.exit(1) 25 | }) 26 | -------------------------------------------------------------------------------- /scripts/deployTimelock.ts: -------------------------------------------------------------------------------- 1 | import {ethers, run} from "hardhat" 2 | 3 | function sleep(ms: number) { 4 | return new Promise(resolve => setTimeout(resolve, ms)) 5 | } 6 | 7 | async function main() { 8 | const [deployer] = await ethers.getSigners() 9 | 10 | console.log("Deploying contracts with the account:", deployer.address) 11 | 12 | const minDelay = 3 * 24 * 60 * 60 // 3 Days 259200 13 | 14 | const multiSig = "" 15 | const proposers = [multiSig] 16 | const executors = [multiSig] 17 | 18 | const TimelockController = await ethers.getContractFactory("SymmioTimelockController") 19 | const timelock = await TimelockController.deploy(minDelay, proposers, executors, multiSig) 20 | 21 | await timelock.waitForDeployment() 22 | 23 | console.log("TimelockController deployed to:", await timelock.getAddress()) 24 | 25 | await sleep(30000) 26 | 27 | await run("verify:verify", { 28 | address: await timelock.getAddress(), 29 | constructorArguments: [minDelay, proposers, executors, multiSig], 30 | contract: "contracts/SymmioTimelockController.sol:SymmioTimelockController" 31 | }) 32 | } 33 | 34 | main() 35 | .then(() => process.exit(0)) 36 | .catch((error) => { 37 | console.error(error) 38 | process.exit(1) 39 | }) -------------------------------------------------------------------------------- /scripts/diamondUpgrade.ts: -------------------------------------------------------------------------------- 1 | import {ethers} from "hardhat" 2 | 3 | 4 | // Main function 5 | async function main() { 6 | const diamondAddress = "" 7 | const [deployer] = await ethers.getSigners() 8 | 9 | const diamondCutFacet = await ethers.getContractAt( 10 | "DiamondCutFacet", 11 | diamondAddress, 12 | deployer 13 | ) 14 | // Prepare _init and _calldata (set to zero address and empty bytes for this example) 15 | const _init = ethers.ZeroAddress // Updated for ethers v6 16 | const _calldata = "0x" 17 | 18 | const diamondCut: any[] = [] 19 | 20 | console.log(diamondCutFacet.interface.encodeFunctionData("diamondCut", [diamondCut, _init, _calldata])) 21 | } 22 | 23 | // Run the main function 24 | main() 25 | .then(() => process.exit(0)) 26 | .catch((error) => { 27 | console.error(error) 28 | process.exit(1) 29 | }) 30 | -------------------------------------------------------------------------------- /scripts/fuzz/WakeUpUser.ts: -------------------------------------------------------------------------------- 1 | import * as fsPromise from "fs/promises" 2 | import { ethers } from "hardhat" 3 | 4 | import { ManagedError } from "../../test/models/ManagedError" 5 | import { createRunContext } from "../../test/models/RunContext" 6 | import { User } from "../../test/models/User" 7 | import { UserController } from "../../test/models/UserController" 8 | import { decimal } from "../../test/utils/Common" 9 | 10 | function sleep(ms: number) { 11 | return new Promise(resolve => setTimeout(resolve, ms)) 12 | } 13 | 14 | async function main() { 15 | const addresses = JSON.parse("" + (await fsPromise.readFile("addresses.json"))) 16 | const context = await createRunContext(addresses.v3Address, addresses.collateralAddress) 17 | const signer = await ethers.getImpersonatedSigner(ethers.Wallet.createRandom().address) 18 | const user = new User(context, signer) 19 | await user.setup() 20 | await user.setNativeBalance(100n ** 18n) 21 | const balance = Number(process.env.TEST_USER_BALANCE != null ? process.env.TEST_USER_BALANCE : "5000") 22 | await user.setBalances(decimal(balance), decimal(balance), decimal(balance)) 23 | const userController = new UserController(context.manager, user, undefined) 24 | await userController.start() 25 | const maxLockedAmountForQuote = Number(process.env.TEST_USER_MAX_QUOTE != null ? process.env.TEST_USER_MAX_QUOTE : "100") 26 | 27 | let count = 0 28 | 29 | const timeout = process.env.TEST_USER_TIMEOUT 30 | if (timeout != null) 31 | setTimeout(() => { 32 | console.log("Sent " + count + " quotes successfully") 33 | process.exit() 34 | }, Number(timeout)) 35 | 36 | while (true) { 37 | try { 38 | await userController.sendQuote(decimal(maxLockedAmountForQuote)) 39 | count++ 40 | } catch (error) { 41 | if (error instanceof ManagedError) { 42 | if (error.message.indexOf("Insufficient funds available") >= 0) { 43 | console.error(error.message) 44 | console.log("Sent " + count + " quotes successfully") 45 | break 46 | } else if (error.message.indexOf("Too many open quotes") >= 0) { 47 | await sleep(500) 48 | } 49 | } else { 50 | process.exitCode = 1 51 | console.error(error) 52 | } 53 | } 54 | } 55 | } 56 | 57 | main().catch(error => { 58 | console.error(error) 59 | process.exitCode = 1 60 | }) 61 | -------------------------------------------------------------------------------- /scripts/gasEstimate.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "hardhat" 2 | 3 | async function main() { 4 | let symmioAddress = "" 5 | let facet = await ethers.getContractAt("PartyBFacet", symmioAddress) 6 | // console.log(await facet.settleUpnl(564)) 7 | } 8 | 9 | // We recommend this pattern to be able to use async/await everywhere 10 | // and properly handle errors. 11 | main().catch(error => { 12 | console.error(error) 13 | process.exitCode = 1 14 | }) 15 | -------------------------------------------------------------------------------- /scripts/updateFacet.ts: -------------------------------------------------------------------------------- 1 | import {ethers} from "hardhat" 2 | import {FacetCutAction, getSelectors} from "../tasks/utils/diamondCut" 3 | 4 | async function main() { 5 | let addr = "" 6 | let facetAddr = "" 7 | const diamondCutFacet = await ethers.getContractAt("DiamondCutFacet", addr) 8 | const NewFacet = await ethers.getContractFactory("PartyAFacet") 9 | const selectors = getSelectors(ethers, NewFacet).selectors 10 | await diamondCutFacet.diamondCut( 11 | [ 12 | { 13 | facetAddress: facetAddr, 14 | action: FacetCutAction.Replace, 15 | functionSelectors: selectors, 16 | }, 17 | ], 18 | ethers.ZeroAddress, 19 | "0x", 20 | ) 21 | } 22 | 23 | // We recommend this pattern to be able to use async/await everywhere 24 | // and properly handle errors. 25 | main().catch(error => { 26 | console.error(error) 27 | process.exitCode = 1 28 | }) 29 | -------------------------------------------------------------------------------- /scripts/utils/file.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs" 2 | 3 | export type Addresses = { 4 | symmioAddress?: string 5 | collateralAddress?: string 6 | multiAccountAddress?: string 7 | hedgerProxyAddress?: string 8 | MulticallAddress?: string 9 | nextQuoteIdVerifierAddress?: string 10 | } 11 | 12 | export function loadAddresses(): Addresses { 13 | let output: Addresses = {} 14 | if (fs.existsSync("./output/addresses.json")) { 15 | output = JSON.parse(fs.readFileSync("./output/addresses.json", "utf8")) 16 | } else { 17 | if (!fs.existsSync("./output")) fs.mkdirSync("./output") 18 | output = {} 19 | fs.writeFileSync("./output/addresses.json", JSON.stringify(output)) 20 | } 21 | return output 22 | } 23 | 24 | export function saveAddresses(content: Addresses): void { 25 | if (!fs.existsSync("./output/addresses.json")) { 26 | if (!fs.existsSync("./output")) fs.mkdirSync("./output") 27 | } 28 | fs.writeFileSync("./output/addresses.json", JSON.stringify(content)) 29 | } 30 | -------------------------------------------------------------------------------- /scripts/verifyAll.ts: -------------------------------------------------------------------------------- 1 | import { run } from "hardhat" 2 | 3 | async function main() { 4 | let facets = { 5 | AccountFacet: "", 6 | ControlFacet: "", 7 | DiamondLoupeFacet: "", 8 | LiquidationFacet: "", 9 | PartyAFacet: "", 10 | PartyBFacet: "", 11 | ViewFacet: "", 12 | FundingRateFacet: "", 13 | } 14 | for (const facet in facets) { 15 | if (!facets.hasOwnProperty(facet)) continue 16 | const facetAddr = (facets as any)[facet] 17 | console.log(`Verifying ${facet} with impl in ${facetAddr}`) 18 | await run("verify:verify", { 19 | address: facetAddr, 20 | constructorArguments: [], 21 | }) 22 | } 23 | } 24 | 25 | // We recommend this pattern to be able to use async/await everywhere 26 | // and properly handle errors. 27 | main().catch(error => { 28 | console.error(error) 29 | process.exitCode = 1 30 | }) 31 | -------------------------------------------------------------------------------- /static-tests: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | ./docker/compile.sh 3 | TEST_MODE=static npx hardhat test 4 | -------------------------------------------------------------------------------- /tasks/deploy/callProxy.ts: -------------------------------------------------------------------------------- 1 | import { sleep } from "@nomicfoundation/hardhat-verify/internal/utilities" 2 | import { task } from "hardhat/config" 3 | 4 | task("deploy:callProxy", "Deploys the CallProxy") 5 | .addParam("admin", "The admin address") 6 | .addParam("whitelist", "The whitelisted target address") 7 | .addParam("operators", "The addresses that can be behind this proxy") 8 | .setAction(async ({ admin, whitelist, operators }, { ethers, upgrades, run }) => { 9 | console.log("Running deploy:callProxy") 10 | 11 | const [deployer] = await ethers.getSigners() 12 | console.log("Deploying contracts with the account:", deployer.address) 13 | 14 | // Get the contract factory and deploy the proxy 15 | const factory = await ethers.getContractFactory("CallProxy") 16 | const contract = await upgrades.deployProxy(factory, [admin], { initializer: "initialize" }) 17 | await contract.waitForDeployment() 18 | 19 | // Retrieve contract addresses for logging 20 | const proxyAddress = await contract.getAddress() 21 | const adminAddress = await upgrades.erc1967.getAdminAddress(proxyAddress) 22 | const implementationAddress = await upgrades.erc1967.getImplementationAddress(proxyAddress) 23 | const addresses = { 24 | proxy: proxyAddress, 25 | admin: adminAddress, 26 | implementation: implementationAddress, 27 | } 28 | console.log("CallProxy deployed to", addresses) 29 | 30 | await sleep(10000) 31 | 32 | // Update whitelist if provided, and wait for the transaction to be mined 33 | if (whitelist) { 34 | const whitelistTx = await contract.setCallWhitelist(whitelist, true) 35 | await whitelistTx.wait() 36 | console.log("Address whitelisted:", whitelist) 37 | } 38 | 39 | // Grant trusted roles to operators if provided 40 | if (operators) { 41 | const operatorsArray = operators.split(",") 42 | const trustedRole = await contract.TRUSTED_ROLE() 43 | for (const operator of operatorsArray) { 44 | const grantTx = await contract.grantRole(trustedRole, operator.trim()) 45 | await grantTx.wait() 46 | console.log("Granted trusted role to", operator.trim()) 47 | } 48 | } 49 | 50 | // Verify the deployed contract 51 | await run("verify:verify", { 52 | address: proxyAddress, 53 | constructorArguments: [], 54 | }) 55 | 56 | return contract 57 | }) 58 | -------------------------------------------------------------------------------- /tasks/deploy/constants.ts: -------------------------------------------------------------------------------- 1 | export const FacetNames = [ 2 | "AccountFacet", 3 | "ControlFacet", 4 | "DiamondLoupeFacet", 5 | "LiquidationFacet", 6 | "PartyAFacet", 7 | "BridgeFacet", 8 | "ViewFacet", 9 | "FundingRateFacet", 10 | "ForceActionsFacet", 11 | "SettlementFacet", 12 | "PartyBPositionActionsFacet", 13 | "PartyBQuoteActionsFacet", 14 | "PartyBGroupActionsFacet", 15 | ] 16 | 17 | export const DEPLOYMENT_LOG_FILE = "deployed.json" -------------------------------------------------------------------------------- /tasks/deploy/feeDistributor.ts: -------------------------------------------------------------------------------- 1 | import {task} from "hardhat/config" 2 | 3 | task("deploy:feeDistributor", "Deploys the SymmioFeeDistributor") 4 | .addParam("symmioAddress", "The address of the Symmio contract") 5 | .addParam("admin", "The admin address") 6 | .addParam("symmioShare", "The symmio share") 7 | .addParam("symmioShareReceiver", "The symmio share receiver") 8 | .setAction(async ({symmioAddress, admin, symmioShareReceiver, symmioShare}, {ethers, upgrades, run}) => { 9 | console.log("Running deploy:feeDistributor") 10 | 11 | const [deployer] = await ethers.getSigners() 12 | 13 | console.log("Deploying contracts with the account:", deployer.address) 14 | 15 | // Deploy SymmioFeeDistributor as upgradeable 16 | const factory = await ethers.getContractFactory("SymmioFeeDistributor") 17 | const contract = await upgrades.deployProxy(factory, [admin, symmioAddress, symmioShareReceiver, symmioShare], {initializer: "initialize"}) 18 | await contract.waitForDeployment() 19 | 20 | const addresses = { 21 | proxy: await contract.getAddress(), 22 | admin: await upgrades.erc1967.getAdminAddress(await contract.getAddress()), 23 | implementation: await upgrades.erc1967.getImplementationAddress(await contract.getAddress()), 24 | } 25 | console.log("SymmioFeeDistributor deployed to", addresses) 26 | 27 | return contract 28 | }) 29 | -------------------------------------------------------------------------------- /tasks/deploy/index.ts: -------------------------------------------------------------------------------- 1 | import "./diamond" 2 | import "./multicall" 3 | import "./stablecoin" 4 | import "./multiaccount" 5 | import "./partyB" 6 | import "./feeDistributor" 7 | import "./nextQuoteIdVerifier" 8 | import "./callProxy" 9 | import "./verify" -------------------------------------------------------------------------------- /tasks/deploy/multiaccount.ts: -------------------------------------------------------------------------------- 1 | import {task, types} from "hardhat/config" 2 | import {readData, writeData} from "../utils/fs" 3 | import {DEPLOYMENT_LOG_FILE} from "./constants" 4 | 5 | task("deploy:multiAccount", "Deploys the MultiAccount") 6 | .addParam("symmioAddress", "The address of the Symmio contract") 7 | .addParam("admin", "The admin address") 8 | .addOptionalParam("logData", "Write the deployed addresses to a data file", true, types.boolean) 9 | .setAction(async ({symmioAddress, admin, logData}, {ethers, upgrades, run}) => { 10 | console.log("Running deploy:multiAccount") 11 | 12 | const [deployer] = await ethers.getSigners() 13 | 14 | console.log("Deploying contracts with the account:", deployer.address) 15 | 16 | const SymmioPartyA = await ethers.getContractFactory("SymmioPartyA") 17 | 18 | // Deploy MultiAccount as upgradeable 19 | const Factory = await ethers.getContractFactory("MultiAccount") 20 | console.log(admin, symmioAddress) 21 | const contract = await upgrades.deployProxy( 22 | Factory, 23 | [admin, symmioAddress, SymmioPartyA.bytecode], 24 | {initializer: "initialize"} 25 | ) 26 | await contract.waitForDeployment() 27 | 28 | const addresses = { 29 | proxy: await contract.getAddress(), 30 | admin: await upgrades.erc1967.getAdminAddress(await contract.getAddress()), 31 | implementation: await upgrades.erc1967.getImplementationAddress(await contract.getAddress()), 32 | } 33 | console.log("MultiAccount deployed to", addresses) 34 | 35 | if (logData) { 36 | // Read existing data 37 | let deployedData = [] 38 | try { 39 | deployedData = readData(DEPLOYMENT_LOG_FILE) 40 | } catch (err) { 41 | console.error(`Could not read existing JSON file: ${err}`) 42 | } 43 | 44 | // Append new data 45 | deployedData.push( 46 | { 47 | name: "MultiAccountProxy", 48 | address: await contract.getAddress(), 49 | constructorArguments: [admin, symmioAddress, SymmioPartyA.bytecode], 50 | }, 51 | { 52 | name: "MultiAccountAdmin", 53 | address: addresses.admin, 54 | constructorArguments: [], 55 | }, 56 | { 57 | name: "MultiAccountImplementation", 58 | address: addresses.implementation, 59 | constructorArguments: [], 60 | } 61 | ) 62 | 63 | // Write updated data back to JSON file 64 | writeData(DEPLOYMENT_LOG_FILE, deployedData) 65 | console.log("Deployed addresses written to JSON file") 66 | } 67 | 68 | return contract 69 | }) 70 | -------------------------------------------------------------------------------- /tasks/deploy/multicall.ts: -------------------------------------------------------------------------------- 1 | import {task, types} from "hardhat/config" 2 | import {readData, writeData} from "../utils/fs" 3 | import {DEPLOYMENT_LOG_FILE} from "./constants" 4 | import {SignerWithAddress} from "@nomicfoundation/hardhat-ethers/signers" 5 | 6 | task("deploy:multicall", "Deploys the Multicall") 7 | .addOptionalParam("logData", "Write the deployed addresses to a data file", true, types.boolean) 8 | .setAction(async ({logData}, {ethers, run}) => { 9 | console.log("Running deploy:multicall") 10 | 11 | const signers: SignerWithAddress[] = await ethers.getSigners() 12 | const owner: SignerWithAddress = signers[0] 13 | 14 | const Factory = await ethers.getContractFactory("Multicall3") 15 | const multicall = await Factory.connect(owner).deploy() 16 | await multicall.waitForDeployment() 17 | 18 | await multicall.deploymentTransaction()!.wait() 19 | console.log("Multicall3 deployed:", await multicall.getAddress()) 20 | 21 | if (logData) { 22 | // Read existing data 23 | let deployedData = [] 24 | try { 25 | deployedData = readData(DEPLOYMENT_LOG_FILE) 26 | } catch (err) { 27 | console.error(`Could not read existing JSON file: ${err}`) 28 | } 29 | 30 | // Append new data 31 | deployedData.push({ 32 | name: "Multicall3", 33 | address: await multicall.getAddress(), 34 | constructorArguments: [], 35 | }) 36 | 37 | // Write updated data back to JSON file 38 | writeData(DEPLOYMENT_LOG_FILE, deployedData) 39 | console.log("Deployed addresses written to JSON file") 40 | } 41 | 42 | return multicall 43 | }) 44 | -------------------------------------------------------------------------------- /tasks/deploy/nextQuoteIdVerifier.ts: -------------------------------------------------------------------------------- 1 | import { task, types } from "hardhat/config" 2 | import { readData, writeData } from "../utils/fs" 3 | import { DEPLOYMENT_LOG_FILE } from "./constants" 4 | import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers" 5 | 6 | task("deploy:next-quote-id-verifier", "Deploys the Next Quote Id Verifier") 7 | .addParam("symmioAddress", "The address of the Symmio contract") 8 | .addOptionalParam("logData", "Write the deployed addresses to a data file", true, types.boolean) 9 | .setAction(async ({ symmioAddress, logData }, { ethers, run }) => { 10 | console.log("Running deploy:next-quote-id-verifier") 11 | 12 | const signers: SignerWithAddress[] = await ethers.getSigners() 13 | const owner: SignerWithAddress = signers[0] 14 | console.log("using address: " + JSON.stringify(owner)) 15 | 16 | const factory = await ethers.getContractFactory("NextQuoteIDVerifier") 17 | const contract = await factory.connect(owner).deploy(symmioAddress) 18 | await contract.waitForDeployment() 19 | 20 | await contract.deploymentTransaction()!.wait() 21 | console.log("NextQuoteIDVerifier deployed:", await contract.getAddress()) 22 | 23 | if (logData) { 24 | // Read existing data 25 | let deployedData = [] 26 | try { 27 | deployedData = readData(DEPLOYMENT_LOG_FILE) 28 | } catch (err) {} 29 | 30 | // Append new data 31 | deployedData.push({ 32 | name: "NextQuoteIDVerifier", 33 | address: await contract.getAddress(), 34 | constructorArguments: [symmioAddress], 35 | }) 36 | 37 | // Write updated data back to JSON file 38 | writeData(DEPLOYMENT_LOG_FILE, deployedData) 39 | console.log("Deployed addresses written to JSON file") 40 | } 41 | 42 | return contract 43 | }) 44 | -------------------------------------------------------------------------------- /tasks/deploy/partyB.ts: -------------------------------------------------------------------------------- 1 | import {task, types} from "hardhat/config" 2 | import {readData, writeData} from "../utils/fs" 3 | import {DEPLOYMENT_LOG_FILE} from "./constants" 4 | 5 | task("deploy:symmioPartyB", "Deploys the SymmioPartyB") 6 | .addParam("symmioAddress", "The address of the Symmio contract") 7 | .addParam("admin", "The admin address") 8 | .addOptionalParam("logData", "Write the deployed addresses to a data file", true, types.boolean) 9 | .setAction(async ({symmioAddress, admin, logData}, {ethers, upgrades, run}) => { 10 | console.log("Running deploy:symmioPartyB") 11 | 12 | const [deployer] = await ethers.getSigners() 13 | 14 | console.log("Deploying contracts with the account:", deployer.address) 15 | 16 | // Deploy SymmioPartyB as upgradeable 17 | const SymmioPartyBFactory = await ethers.getContractFactory("SymmioPartyB") 18 | const symmioPartyB = await upgrades.deployProxy(SymmioPartyBFactory, [admin, symmioAddress], {initializer: "initialize"}) 19 | await symmioPartyB.waitForDeployment() 20 | 21 | const addresses = { 22 | proxy: await symmioPartyB.getAddress(), 23 | admin: await upgrades.erc1967.getAdminAddress(await symmioPartyB.getAddress()), 24 | implementation: await upgrades.erc1967.getImplementationAddress(await symmioPartyB.getAddress()), 25 | } 26 | console.log("SymmioPartyB deployed to", addresses) 27 | 28 | // Update the deployed addresses JSON file 29 | if (logData) { 30 | let deployedData = [] 31 | try { 32 | deployedData = readData(DEPLOYMENT_LOG_FILE) 33 | } catch (err) { 34 | console.error(`Could not read existing JSON file: ${err}`) 35 | } 36 | 37 | // Append new data 38 | deployedData.push( 39 | { 40 | name: "SymmioPartyBProxy", 41 | address: await symmioPartyB.getAddress(), 42 | constructorArguments: [admin, symmioAddress], 43 | }, 44 | { 45 | name: "SymmioPartyBAdmin", 46 | address: addresses.admin, 47 | constructorArguments: [], 48 | }, 49 | { 50 | name: "SymmioPartyBImplementation", 51 | address: addresses.implementation, 52 | constructorArguments: [], 53 | } 54 | ) 55 | 56 | // Write updated data back to JSON file 57 | writeData(DEPLOYMENT_LOG_FILE, deployedData) 58 | console.log("Deployed addresses written to JSON file") 59 | } 60 | 61 | return symmioPartyB 62 | }) 63 | -------------------------------------------------------------------------------- /tasks/deploy/stablecoin.ts: -------------------------------------------------------------------------------- 1 | import {task, types} from "hardhat/config" 2 | import {readData, writeData} from "../utils/fs" 3 | import {DEPLOYMENT_LOG_FILE} from "./constants" 4 | import {SignerWithAddress} from "@nomicfoundation/hardhat-ethers/signers" 5 | 6 | task("deploy:stablecoin", "Deploys the FakeStablecoin") 7 | .addOptionalParam("logData", "Write the deployed addresses to a data file", true, types.boolean) 8 | .setAction(async ({logData}, {ethers, run}) => { 9 | console.log("Running deploy:stablecoin") 10 | 11 | const signers: SignerWithAddress[] = await ethers.getSigners() 12 | const owner: SignerWithAddress = signers[0] 13 | console.log("using address: " + JSON.stringify(owner)) 14 | 15 | const StablecoinFactory = await ethers.getContractFactory("FakeStablecoin") 16 | const stablecoin = await StablecoinFactory.connect(owner).deploy() 17 | await stablecoin.waitForDeployment() 18 | 19 | await stablecoin.deploymentTransaction()!.wait() 20 | console.log("FakeStablecoin deployed:", await stablecoin.getAddress()) 21 | 22 | if (logData) { 23 | // Read existing data 24 | let deployedData = [] 25 | try { 26 | deployedData = readData(DEPLOYMENT_LOG_FILE) 27 | } catch (err) { 28 | } 29 | 30 | // Append new data 31 | deployedData.push({ 32 | name: "FakeStablecoin", 33 | address: await stablecoin.getAddress(), 34 | constructorArguments: [], 35 | }) 36 | 37 | // Write updated data back to JSON file 38 | writeData(DEPLOYMENT_LOG_FILE, deployedData) 39 | console.log("Deployed addresses written to JSON file") 40 | } 41 | 42 | return stablecoin 43 | }) 44 | -------------------------------------------------------------------------------- /tasks/deploy/verify.ts: -------------------------------------------------------------------------------- 1 | import {task} from "hardhat/config" 2 | 3 | import {readData} from "../utils/fs" 4 | import {DEPLOYMENT_LOG_FILE} from "./constants" 5 | 6 | 7 | task("verify:deployment", "Verifies the deployed contracts").setAction(async (_, {run}) => { 8 | const deployedAddresses = readData(DEPLOYMENT_LOG_FILE) 9 | 10 | for (const address of deployedAddresses) { 11 | try { 12 | console.log(`Verifying ${address.address}`) 13 | await run("verify:verify", { 14 | address: address.address, 15 | constructorArguments: address.constructorArguments, 16 | }) 17 | } catch (err) { 18 | console.error(err) 19 | } 20 | } 21 | }) -------------------------------------------------------------------------------- /tasks/utils/diamondCut.ts: -------------------------------------------------------------------------------- 1 | import {Contract, ContractFactory} from "ethers" 2 | 3 | export enum FacetCutAction { 4 | Add = 0, 5 | Replace = 1, 6 | Remove = 2, 7 | } 8 | 9 | 10 | export function getSelectors(ethers: any, contract: Contract | ContractFactory) { 11 | const functions = contract.interface.fragments.filter( 12 | (f) => f.type === 'function' 13 | ) 14 | const selectors = functions.reduce((acc: string[], f) => { 15 | const signature = f.format('sighash') 16 | if (signature !== 'init(bytes)') { 17 | acc.push(ethers.id(signature).substring(0, 10)) 18 | } 19 | return acc 20 | }, []) 21 | 22 | const remove = (functionNames: string[]) => { 23 | const sigHashesToRemove = functionNames.map((name) => { 24 | const fragment = contract.interface.getFunction(name)! 25 | return ethers.id(fragment.format('sighash')).substring(0, 10) 26 | }) 27 | return selectors.filter((val) => !sigHashesToRemove.includes(val)) 28 | } 29 | 30 | const get = (functionNames: string[]) => { 31 | const sigHashesToGet = functionNames.map((name) => { 32 | const fragment = contract.interface.getFunction(name)! 33 | return ethers.id(fragment.format('sighash')).substring(0, 10) 34 | }) 35 | return selectors.filter((val) => sigHashesToGet.includes(val)) 36 | } 37 | 38 | return { 39 | selectors, 40 | remove, 41 | get, 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tasks/utils/fs.ts: -------------------------------------------------------------------------------- 1 | import fs, {writeFileSync} from "fs" 2 | import {dirname} from "path" 3 | 4 | const BASE_PATH = "./tasks/data" 5 | 6 | export function readData(fileName: string): any { 7 | createDirectory(BASE_PATH) 8 | return JSON.parse(fs.readFileSync(`${BASE_PATH}/${fileName}`, "utf8")) 9 | } 10 | 11 | export function writeData(relativePath: string, data: object): void { 12 | createDirectory(BASE_PATH) 13 | const dirPath = dirname(relativePath) 14 | createDirectory(dirPath) 15 | writeFileSync(`${BASE_PATH}/${relativePath}`, JSON.stringify(data, null, 2)) 16 | } 17 | 18 | export function createDirectory(path: string): void { 19 | if (!fs.existsSync(path)) { 20 | fs.mkdirSync(path, {recursive: true}) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tasks/utils/gas.ts: -------------------------------------------------------------------------------- 1 | import {JsonRpcProvider} from "@ethersproject/providers" 2 | import {formatEther} from "@ethersproject/units" 3 | 4 | export async function generateGasReport(provider: JsonRpcProvider, totalGasUsed: bigint): Promise { 5 | const gasPrice = BigInt((await provider.getGasPrice()).toString()) 6 | console.log("Total Gas Consumption: ") 7 | console.table({ 8 | gasUsed: totalGasUsed.toString(), 9 | gasPrice: gasPrice.toString(), 10 | gasCostEther: formatEther(totalGasUsed * gasPrice).toString(), 11 | }) 12 | } 13 | -------------------------------------------------------------------------------- /test/Diamond.behavior.ts: -------------------------------------------------------------------------------- 1 | import {loadFixture} from "@nomicfoundation/hardhat-network-helpers" 2 | import {assert, expect} from "chai" 3 | import {ethers} from "hardhat" 4 | 5 | import {FacetCutAction, getSelectors} from "../tasks/utils/diamondCut" 6 | import {initializeFixture} from "./Initialize.fixture" 7 | import {RunContext} from "./models/RunContext" 8 | 9 | function haveSameMembers(array1: any[], array2: any[]) { 10 | if (array1.length !== array2.length) { 11 | return false 12 | } 13 | 14 | const set1 = new Set(array1) 15 | const set2 = new Set(array2) 16 | 17 | if (set1.size !== set2.size) { 18 | return false 19 | } 20 | 21 | for (let item of set1) { 22 | if (!set2.has(item)) { 23 | return false 24 | } 25 | } 26 | 27 | return true 28 | } 29 | 30 | export function shouldBehaveLikeDiamond(): void { 31 | const addresses: string[] = [] 32 | let selectors: string[] = [] 33 | let result: string[] = [] 34 | 35 | before(async function () { 36 | this.context = await loadFixture(initializeFixture) 37 | }) 38 | 39 | it("should have 14 facets", async function () { 40 | const context: RunContext = this.context 41 | for (const address of await context.diamondLoupeFacet.facetAddresses()) { 42 | addresses.push(address) 43 | } 44 | assert.equal(addresses.length, 14) 45 | }) 46 | 47 | it("facets should have the right function selectors -- call to facetFunctionSelectors function", async function () { 48 | const context: RunContext = this.context 49 | // DiamondLoupeFacet 50 | selectors = getSelectors(ethers, context.diamondLoupeFacet as any).selectors 51 | result = await context.diamondLoupeFacet.facetFunctionSelectors(addresses[3]) 52 | expect(haveSameMembers(result, selectors)).to.be.true 53 | }) 54 | 55 | it("should remove a function from ViewFacet -- getAccountBalance()", async function () { 56 | const context: RunContext = this.context 57 | const viewFacet = await ethers.getContractFactory("ViewFacet") 58 | const selectors = getSelectors(ethers, viewFacet as any).get(["balanceOf(address)"]) 59 | const viewFacetAddress = addresses[7] 60 | 61 | const tx = await context.diamondCutFacet.diamondCut( 62 | [ 63 | { 64 | facetAddress: ethers.ZeroAddress, 65 | action: FacetCutAction.Remove, 66 | functionSelectors: selectors, 67 | }, 68 | ], 69 | ethers.ZeroAddress, 70 | "0x", 71 | {gasLimit: 800000}, 72 | ) 73 | const receipt = await tx.wait() 74 | 75 | if (!receipt?.status) { 76 | throw new Error(`Diamond upgrade failed: ${tx.hash}`) 77 | } 78 | 79 | const result = await context.diamondLoupeFacet.facetFunctionSelectors(viewFacetAddress) 80 | expect(haveSameMembers(result, getSelectors(ethers, viewFacet as any).remove(["balanceOf(address)"]))).to.be.true 81 | }) 82 | 83 | it("should add the getAccountBalance() function back", async function () { 84 | const context: RunContext = this.context 85 | const viewFacet = await ethers.getContractFactory("ViewFacet") 86 | const viewFacetAddress = addresses[7] 87 | 88 | const tx = await context.diamondCutFacet.diamondCut( 89 | [ 90 | { 91 | facetAddress: viewFacetAddress, 92 | action: FacetCutAction.Add, 93 | functionSelectors: getSelectors(ethers, viewFacet as any).get(["balanceOf(address)"]), 94 | }, 95 | ], 96 | ethers.ZeroAddress, 97 | "0x", 98 | {gasLimit: 800000}, 99 | ) 100 | const receipt = await tx.wait() 101 | 102 | if (!receipt?.status) { 103 | throw new Error(`Diamond upgrade failed: ${tx.hash}`) 104 | } 105 | 106 | const result = await context.diamondLoupeFacet.facetFunctionSelectors(viewFacetAddress) 107 | expect(haveSameMembers(result, getSelectors(ethers, viewFacet as any).selectors)).to.be.true 108 | }) 109 | } 110 | -------------------------------------------------------------------------------- /test/FuzzTest.behavior.ts: -------------------------------------------------------------------------------- 1 | import {ethers} from "hardhat" 2 | import {interval} from "rxjs" 3 | import {Hedger} from "./models/Hedger" 4 | import {HedgerController} from "./models/HedgerController" 5 | import {ManagedError} from "./models/ManagedError" 6 | import {createRunContext, RunContext} from "./models/RunContext" 7 | import {User} from "./models/User" 8 | import {UserController} from "./models/UserController" 9 | import {decimal} from "./utils/Common" 10 | import fsPromise from "fs/promises" 11 | import {BigNumber} from "ethers" 12 | import {QuoteCheckpoint} from "./models/quoteCheckpoint" 13 | 14 | export function shouldBehaveLikeFuzzTest(): void { 15 | beforeEach(async function () { 16 | const addresses = JSON.parse("" + (await fsPromise.readFile(join(__dirname, "..", "output", "addresses.json")))) 17 | this.context = await createRunContext(addresses.v3Address, addresses.collateralAddress) 18 | }) 19 | 20 | it("Should run fine", async function () { 21 | const context: RunContext = this.context 22 | const manager = context.manager 23 | const checkpoint = QuoteCheckpoint.getInstance() 24 | 25 | const uSigner = await ethers.getImpersonatedSigner(ethers.Wallet.createRandom().address) 26 | const user = new User(context, uSigner) 27 | await user.setup() 28 | await user.setNativeBalance(100n ** 18n) 29 | const userController = new UserController(manager, user, checkpoint) 30 | 31 | const hSigner = await ethers.getImpersonatedSigner(ethers.Wallet.createRandom().address) 32 | const hedger = new Hedger(context, hSigner) 33 | await hedger.setup() 34 | await hedger.setNativeBalance(100n ** 18n) 35 | await hedger.setBalances(BigNumber.from("10").pow(`50`), BigNumber.from("10").pow(`50`)) 36 | await hedger.register() 37 | const hedgerController = new HedgerController(manager, hedger, checkpoint) 38 | 39 | await userController.start() 40 | await hedgerController.start() 41 | await user.setBalances(decimal(100000), decimal(100000), decimal(100000)) 42 | 43 | const subscription = interval(1000).subscribe(() => { 44 | manager.actionsLoop.next({ 45 | title: "SendQuote", 46 | action: () => { 47 | return new Promise((resolve, reject) => { 48 | if (manager.getPauseState()) { 49 | reject() 50 | } 51 | userController 52 | .sendQuote() 53 | .then(() => { 54 | resolve() 55 | }) 56 | .catch(error => { 57 | if (error instanceof ManagedError) { 58 | if (error.message.indexOf("Insufficient funds available") >= 0) { 59 | console.error(error.message) 60 | subscription.unsubscribe() 61 | } else if (error.message.indexOf("Too many open quotes") >= 0) { 62 | // DO nothing 63 | } 64 | resolve() 65 | } else { 66 | reject(error) 67 | process.exitCode = 1 68 | console.error(error) 69 | } 70 | }) 71 | }) 72 | }, 73 | }) 74 | }) 75 | 76 | await new Promise(r => setTimeout(r, 200000)) 77 | }) 78 | } 79 | -------------------------------------------------------------------------------- /test/SpecificScenario.behavior.ts: -------------------------------------------------------------------------------- 1 | import {loadFixture} from "@nomicfoundation/hardhat-network-helpers" 2 | import {Builder} from "builder-pattern" 3 | import {ethers} from "hardhat" 4 | 5 | import {initializeFixture} from "./Initialize.fixture" 6 | import {OrderType, PositionType} from "./models/Enums" 7 | import {Hedger} from "./models/Hedger" 8 | import {RunContext} from "./models/RunContext" 9 | import {User} from "./models/User" 10 | import {OpenRequest} from "./models/requestModels/OpenRequest" 11 | import {QuoteRequest} from "./models/requestModels/QuoteRequest" 12 | import {decimal} from "./utils/Common" 13 | import {getDummySingleUpnlAndPriceSig} from "./utils/SignatureUtils" 14 | 15 | export function shouldBehaveLikeSpecificScenario(): void { 16 | beforeEach(async function () { 17 | this.context = await loadFixture(initializeFixture) 18 | }) 19 | 20 | it("Closing position with allocated less than quote value and with positive upnl", async function () { 21 | const context: RunContext = this.context 22 | 23 | const uSigner = await ethers.getImpersonatedSigner(ethers.Wallet.createRandom().address) 24 | const user = new User(context, uSigner) 25 | await user.setup() 26 | await user.setNativeBalance(100n ** 18n) 27 | 28 | const hSigner = await ethers.getImpersonatedSigner(ethers.Wallet.createRandom().address) 29 | const hedger = new Hedger(context, hSigner) 30 | await hedger.setNativeBalance(100n ** 18n) 31 | await hedger.setBalances(decimal(50000n), decimal(50000n)) 32 | await hedger.register() 33 | 34 | let b = decimal(5000n) 35 | await user.setBalances(b, b, b) 36 | 37 | await user.sendQuote( 38 | Builder() 39 | .partyBWhiteList([]) 40 | .quantity("32000000000000000") 41 | .partyAmm("69706470325210735106") 42 | .partyBmm("69706470325210735106") 43 | .cva("14394116573201404621") 44 | .lf("8104916153486468905") 45 | .price("22207600000000000000000") 46 | .upnlSig(getDummySingleUpnlAndPriceSig(BigInt("20817400000000000000000"))) 47 | .maxFundingRate(0) 48 | .symbolId(1) 49 | .orderType(OrderType.MARKET) 50 | .positionType(PositionType.SHORT) 51 | .deadline("100000000000000000") 52 | .build(), 53 | ) 54 | await hedger.lockQuote(1) 55 | await hedger.openPosition( 56 | 1, 57 | Builder() 58 | .filledAmount("32000000000000000") 59 | .openPrice("22207600000000000000000") 60 | .price("20817400000000000000000") 61 | .upnlPartyA(0) 62 | .upnlPartyB(0) 63 | .build(), 64 | ) 65 | // await user.requestToClosePosition( 66 | // 1, 67 | // Builder() 68 | // .closePrice("22944000000000000000") 69 | // .orderType(OrderType.LIMIT) 70 | // .quantityToClose("197200000000000000000") 71 | // .deadline("1000000000000000") 72 | // .upnl(0) 73 | // .build(), 74 | // ); 75 | // await context.accountFacet 76 | // .connect(uSigner) 77 | // .deallocate("4376707987620000000000", await getDummySingleUpnlSig("0")); 78 | // console.log(await user.getBalanceInfo()); 79 | 80 | // await context.partyBFacet 81 | // .connect(hSigner) 82 | // .deallocateForPartyB( 83 | // "4746758351632000000000", 84 | // await user.getAddress(), 85 | // await getDummySingleUpnlSig("531317547460000000000"), 86 | // ); 87 | // console.log(await hedger.getBalanceInfo(await user.getAddress())); 88 | // await hedger.fillCloseRequest( 89 | // 1, 90 | // Builder() 91 | // .filledAmount("197200000000000000000") 92 | // .closedPrice("22919000000000000000") 93 | // .upnlPartyA("-513272021960000000000") 94 | // .upnlPartyB("513277955708000000000") 95 | // .price("22885951200000000000") 96 | // .build(), 97 | // ); 98 | }) 99 | } 100 | -------------------------------------------------------------------------------- /test/models/Actions.ts: -------------------------------------------------------------------------------- 1 | import {QuoteStatus} from "./Enums" 2 | 3 | export enum Action { 4 | CANCEL_REQUEST, 5 | ACCEPT_CANCEL_REQUEST, 6 | LOCK_QUOTE, 7 | UNLOCK_QUOTE, 8 | OPEN_POSITION, 9 | CLOSE_REQUEST, 10 | FORCE_CLOSE_REQUEST, 11 | CANCEL_CLOSE_REQUEST, 12 | ACCEPT_CANCEL_CLOSE_REQUEST, 13 | FILL_POSITION, 14 | NOTHING, 15 | } 16 | 17 | export class ActionWrapper { 18 | constructor(public action: Action, public probability: number = 1, public rethink: boolean = false) { 19 | } 20 | } 21 | 22 | export const actionNamesMap: Map = new Map([ 23 | [Action.CANCEL_REQUEST, "CANCEL_REQUEST"], 24 | [Action.ACCEPT_CANCEL_REQUEST, "ACCEPT_CANCEL_REQUEST"], 25 | [Action.LOCK_QUOTE, "LOCK_QUOTE"], 26 | [Action.UNLOCK_QUOTE, "UNLOCK_QUOTE"], 27 | [Action.OPEN_POSITION, "OPEN_POSITION"], 28 | [Action.CLOSE_REQUEST, "CLOSE_REQUEST"], 29 | [Action.FORCE_CLOSE_REQUEST, "FORCE_CLOSE_REQUEST"], 30 | [Action.CANCEL_CLOSE_REQUEST, "CANCEL_CLOSE_REQUEST"], 31 | [Action.ACCEPT_CANCEL_CLOSE_REQUEST, "ACCEPT_CANCEL_CLOSE_REQUEST"], 32 | [Action.FILL_POSITION, "FILL_POSITION"], 33 | [Action.NOTHING, "NOTHING"], 34 | ]) 35 | 36 | export const userActionsMap: Map = new Map([ 37 | [QuoteStatus.PENDING, [new ActionWrapper(Action.CANCEL_REQUEST, 2), new ActionWrapper(Action.NOTHING, 8)]], 38 | [QuoteStatus.LOCKED, [new ActionWrapper(Action.CANCEL_REQUEST, 2), new ActionWrapper(Action.NOTHING, 8)]], 39 | [QuoteStatus.CANCEL_PENDING, [new ActionWrapper(Action.NOTHING)]], 40 | [QuoteStatus.CANCELED, [new ActionWrapper(Action.NOTHING)]], 41 | [QuoteStatus.OPENED, [new ActionWrapper(Action.CLOSE_REQUEST, 10), new ActionWrapper(Action.NOTHING, 1, true)]], 42 | [ 43 | QuoteStatus.CLOSE_PENDING, 44 | [new ActionWrapper(Action.CANCEL_CLOSE_REQUEST, 1), new ActionWrapper(Action.NOTHING, 1), new ActionWrapper(Action.FORCE_CLOSE_REQUEST, 7)], 45 | ], 46 | [QuoteStatus.CANCEL_CLOSE_PENDING, [new ActionWrapper(Action.NOTHING)]], 47 | [QuoteStatus.CLOSED, [new ActionWrapper(Action.NOTHING)]], 48 | [QuoteStatus.LIQUIDATED, [new ActionWrapper(Action.NOTHING)]], 49 | [QuoteStatus.EXPIRED, [new ActionWrapper(Action.NOTHING)]], 50 | ]) 51 | 52 | export const hedgerActionsMap: Map = new Map([ 53 | [QuoteStatus.PENDING, [new ActionWrapper(Action.LOCK_QUOTE)]], 54 | [QuoteStatus.LOCKED, [new ActionWrapper(Action.UNLOCK_QUOTE, 1), new ActionWrapper(Action.OPEN_POSITION, 4)]], 55 | [QuoteStatus.CANCEL_PENDING, [new ActionWrapper(Action.ACCEPT_CANCEL_REQUEST, 1), new ActionWrapper(Action.OPEN_POSITION, 1)]], 56 | [QuoteStatus.CANCELED, [new ActionWrapper(Action.NOTHING)]], 57 | [QuoteStatus.OPENED, [new ActionWrapper(Action.NOTHING)]], 58 | [QuoteStatus.CLOSE_PENDING, [new ActionWrapper(Action.FILL_POSITION)]], //TODO : Review 59 | [QuoteStatus.CANCEL_CLOSE_PENDING, [new ActionWrapper(Action.FILL_POSITION, 1), new ActionWrapper(Action.ACCEPT_CANCEL_CLOSE_REQUEST, 2)]], 60 | [QuoteStatus.CLOSED, [new ActionWrapper(Action.NOTHING)]], 61 | [QuoteStatus.LIQUIDATED, [new ActionWrapper(Action.NOTHING)]], 62 | [QuoteStatus.EXPIRED, [new ActionWrapper(Action.NOTHING)]], 63 | ]) 64 | 65 | export function expandActions(wrappers: ActionWrapper[]): ActionWrapper[] { 66 | let actions: ActionWrapper[] = [] 67 | for (const wrapper of wrappers) for (let i = 0; i < wrapper.probability; i++) actions.push(wrapper) 68 | return actions 69 | } 70 | -------------------------------------------------------------------------------- /test/models/BalanceInfo.ts: -------------------------------------------------------------------------------- 1 | export class BalanceInfo { 2 | public balance: bigint 3 | public allocated: bigint 4 | public pendingLocked: bigint 5 | public locked: bigint 6 | 7 | constructor() { 8 | this.balance = 0n 9 | this.allocated = 0n 10 | this.pendingLocked = 0n 11 | this.locked = 0n 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/models/Enums.ts: -------------------------------------------------------------------------------- 1 | export enum QuoteStatus { 2 | PENDING, 3 | LOCKED, 4 | CANCEL_PENDING, 5 | CANCELED, 6 | OPENED, 7 | CLOSE_PENDING, 8 | CANCEL_CLOSE_PENDING, 9 | CLOSED, 10 | LIQUIDATED, 11 | EXPIRED, 12 | LIQUIDATED_PENDING, 13 | } 14 | 15 | export enum PositionType { 16 | LONG, 17 | SHORT, 18 | } 19 | 20 | export enum OrderType { 21 | LIMIT, 22 | MARKET, 23 | } 24 | 25 | export enum Event { 26 | SEND_QUOTE = "SendQuote", 27 | REQUEST_TO_CANCEL_QUOTE = "RequestToCancelQuote", 28 | REQUEST_TO_CLOSE_POSITION = "RequestToClosePosition", 29 | REQUEST_TO_CANCEL_CLOSE_REQUEST = "RequestToCancelCloseRequest", 30 | LOCK_QUOTE = "LockQuote", 31 | UNLOCK_QUOTE = "UnlockQuote", 32 | ACCEPT_CANCEL_REQUEST = "AcceptCancelRequest", 33 | OPEN_POSITION = "OpenPosition", 34 | ACCEPT_CANCEL_CLOSE_REQUEST = "AcceptCancelCloseRequest", 35 | FILL_CLOSE_REQUEST = "FillCloseRequest", 36 | DEPOSIT = "Deposit", 37 | WITHDRAW = "Withdraw", 38 | ALLOCATE_PARTYA = "AllocatePartyA", 39 | DEALLOCATE_PARTYA = "DeallocatePartyA", 40 | LIQUIDATE_PARTYA = "LiquidatePartyA", 41 | LIQUIDATE_POSITIONS_PARTYA = "LiquidatePositionsPartyA", 42 | FULLY_LIQUIDATED_PARTYA = "FullyLiquidatedPartyA", 43 | LIQUIDATE_PARTYB = "LiquidatePartyB", 44 | LIQUIDATE_POSITIONS_PARTYB = "LiquidatePositionsPartyB", 45 | FULLY_LIQUIDATED_PARTYB = "FullyLiquidatedPartyB", 46 | EXPIRE_QUOTE_OPEN = "ExpireQuoteOpen", 47 | EXPIRE_QUOTE_CLOSE = "ExpireQuoteClose", 48 | } 49 | 50 | export enum LiquidationType { 51 | NONE, 52 | NORMAL, 53 | LATE, 54 | OVERDUE, 55 | } 56 | 57 | export enum BridgeTransactionStatus { 58 | RECEIVED, 59 | SUSPENDED, 60 | WITHDRAWN, 61 | } 62 | 63 | export enum BridgeStatus { 64 | NOT_WHITELIST, 65 | WHITELIST, 66 | SUSPEND, 67 | REMOVE, 68 | } 69 | -------------------------------------------------------------------------------- /test/models/ManagedError.ts: -------------------------------------------------------------------------------- 1 | export class ManagedError extends Error { 2 | constructor(msg: string) { 3 | super(msg) 4 | 5 | // Set the prototype explicitly. 6 | Object.setPrototypeOf(this, ManagedError.prototype) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/models/SymbolManager.ts: -------------------------------------------------------------------------------- 1 | interface Symbol { 2 | name: any 3 | symbol: any 4 | asset: any 5 | symbol_id: any 6 | price_precision: number 7 | quantity_precision: number 8 | is_valid: boolean 9 | min_acceptable_quote_value: number 10 | min_acceptable_portion_lf: number 11 | trading_fee: number 12 | } 13 | 14 | export let symbolsMock = { 15 | symbols: [ 16 | { 17 | name: "BTCUSDT", 18 | symbol: "BTC", 19 | asset: "USDT", 20 | symbol_id: 1, 21 | price_precision: 1, 22 | quantity_precision: 3, 23 | is_valid: true, 24 | min_acceptable_quote_value: BigInt("60000000000000000000"), 25 | min_acceptable_portion_lf: 4000000000000000, 26 | trading_fee: 1000000000000000, 27 | }, 28 | { 29 | name: "ETHUSDT", 30 | symbol: "ETH", 31 | asset: "USDT", 32 | symbol_id: 2, 33 | price_precision: 2, 34 | quantity_precision: 3, 35 | is_valid: true, 36 | min_acceptable_quote_value: BigInt("60000000000000000000"), 37 | min_acceptable_portion_lf: 4000000000000000, 38 | trading_fee: 1000000000000000, 39 | }, 40 | { 41 | name: "BCHUSDT", 42 | symbol: "BCH", 43 | asset: "USDT", 44 | symbol_id: 3, 45 | price_precision: 2, 46 | quantity_precision: 3, 47 | is_valid: true, 48 | min_acceptable_quote_value: BigInt("20000000000000000000"), 49 | min_acceptable_portion_lf: 4000000000000000, 50 | trading_fee: 1000000000000000, 51 | }, 52 | { 53 | name: "XRPUSDT", 54 | symbol: "XRP", 55 | asset: "USDT", 56 | symbol_id: 4, 57 | price_precision: 4, 58 | quantity_precision: 1, 59 | is_valid: true, 60 | min_acceptable_quote_value: BigInt("20000000000000000000"), 61 | min_acceptable_portion_lf: 4000000000000000, 62 | trading_fee: 1000000000000000, 63 | }, 64 | { 65 | name: "EOSUSDT", 66 | symbol: "EOS", 67 | asset: "USDT", 68 | symbol_id: 5, 69 | price_precision: 3, 70 | quantity_precision: 1, 71 | is_valid: true, 72 | min_acceptable_quote_value: BigInt("20000000000000000000"), 73 | min_acceptable_portion_lf: 4000000000000000, 74 | trading_fee: 1000000000000000, 75 | }, 76 | ], 77 | } 78 | 79 | export class SymbolManager { 80 | symbols: Map = new Map() 81 | 82 | constructor() { 83 | } 84 | 85 | public async loadSymbols() { 86 | if (process.env.TEST_MODE != "fuzz") return 87 | try { 88 | // let result = await fetch(`${ process.env.HEDGER_WEB_SERVICE }/contract-symbols`) 89 | let jsonResult = symbolsMock 90 | for (const symbol of jsonResult["symbols"]) { 91 | this.symbols.set(symbol.symbol_id, symbol as any) 92 | } 93 | } catch { 94 | throw new Error(`Failed to fetch symbols. Is server up and running?`) 95 | } 96 | } 97 | 98 | public getSymbolPricePrecision(symbolId: number): number { 99 | if (!this.symbols.has(symbolId)) return 100 100 | return this.symbols.get(symbolId)!.price_precision 101 | } 102 | 103 | public getSymbolQuantityPrecision(symbolId: number): number { 104 | if (!this.symbols.has(symbolId)) return 100 105 | return this.symbols.get(symbolId)!.quantity_precision 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /test/models/quoteCheckpoint.ts: -------------------------------------------------------------------------------- 1 | export class QuoteCheckpoint { 2 | private static instance: QuoteCheckpoint | null = null 3 | private _blockedQuotes: Map = new Map() 4 | 5 | private constructor() { 6 | } 7 | 8 | public static getInstance(): QuoteCheckpoint { 9 | if (!QuoteCheckpoint.instance) { 10 | QuoteCheckpoint.instance = new QuoteCheckpoint() 11 | } 12 | 13 | return QuoteCheckpoint.instance 14 | } 15 | 16 | public addBlockedQuotes(quoteId: bigint): void { 17 | this._blockedQuotes.set(quoteId.toString(), true) 18 | } 19 | 20 | public deleteBlockedQuotes(quoteId: bigint): void { 21 | this._blockedQuotes.set(quoteId.toString(), false) 22 | } 23 | 24 | public isBlockedQuote(quoteId: bigint): boolean | undefined { 25 | console.log(this._blockedQuotes.keys()) 26 | return this._blockedQuotes.get(quoteId.toString()) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /test/models/requestModels/CloseRequest.ts: -------------------------------------------------------------------------------- 1 | import {Builder} from "builder-pattern" 2 | 3 | import {decimal, getBlockTimestamp} from "../../utils/Common" 4 | import {OrderType} from "../Enums" 5 | 6 | export interface CloseRequest { 7 | quantityToClose: bigint; 8 | closePrice: bigint; 9 | price: bigint; 10 | upnl: bigint; 11 | orderType: OrderType; 12 | deadline: Promise | bigint; 13 | } 14 | 15 | const limitDefaultCloseRequest: CloseRequest = { 16 | quantityToClose: decimal(100n), 17 | closePrice: decimal(1n), 18 | price: decimal(1n), 19 | upnl: 0n, 20 | orderType: OrderType.LIMIT, 21 | deadline: getBlockTimestamp(500n) as Promise, 22 | } 23 | 24 | const marketDefaultCloseRequest: CloseRequest = { 25 | quantityToClose: decimal(1000n), 26 | closePrice: decimal(1n), 27 | price: decimal(1n), 28 | upnl: 0n, 29 | orderType: OrderType.MARKET, 30 | deadline: getBlockTimestamp(500n) as Promise, 31 | } 32 | 33 | export const limitCloseRequestBuilder = () => Builder(limitDefaultCloseRequest) 34 | export const marketCloseRequestBuilder = () => Builder(marketDefaultCloseRequest) 35 | -------------------------------------------------------------------------------- /test/models/requestModels/EmergencyCloseRequest.ts: -------------------------------------------------------------------------------- 1 | import {Builder} from "builder-pattern" 2 | import {BigNumberish} from "ethers" 3 | 4 | import {decimal} from "../../utils/Common" 5 | 6 | export interface EmergencyCloseRequest { 7 | upnlPartyA: BigNumberish 8 | upnlPartyB: BigNumberish 9 | price: BigNumberish 10 | } 11 | 12 | const defaultEmergencyCloseRequest: EmergencyCloseRequest = { 13 | upnlPartyA: 0, 14 | upnlPartyB: 0, 15 | price: decimal(1n), 16 | } 17 | 18 | export const emergencyCloseRequestBuilder = () => Builder(defaultEmergencyCloseRequest) 19 | -------------------------------------------------------------------------------- /test/models/requestModels/FillCloseRequest.ts: -------------------------------------------------------------------------------- 1 | import {Builder} from "builder-pattern" 2 | import {BigNumberish} from "ethers" 3 | 4 | import {decimal} from "../../utils/Common" 5 | 6 | export interface FillCloseRequest { 7 | filledAmount: BigNumberish 8 | closedPrice: BigNumberish 9 | upnlPartyA: BigNumberish 10 | upnlPartyB: BigNumberish 11 | price: BigNumberish 12 | } 13 | 14 | const limitDefaultFillCloseRequest: FillCloseRequest = { 15 | filledAmount: decimal(100n), 16 | closedPrice: decimal(1n), 17 | upnlPartyA: 0, 18 | upnlPartyB: 0, 19 | price: decimal(1n), 20 | } 21 | 22 | const marketDefaultFillCloseRequest: FillCloseRequest = { 23 | filledAmount: decimal(1000n), 24 | closedPrice: decimal(1n), 25 | upnlPartyA: 0, 26 | upnlPartyB: 0, 27 | price: decimal(1n), 28 | } 29 | 30 | export const limitFillCloseRequestBuilder = () => Builder(limitDefaultFillCloseRequest) 31 | export const marketFillCloseRequestBuilder = () => Builder(marketDefaultFillCloseRequest) 32 | -------------------------------------------------------------------------------- /test/models/requestModels/OpenRequest.ts: -------------------------------------------------------------------------------- 1 | import {Builder} from "builder-pattern" 2 | import {BigNumberish} from "ethers" 3 | 4 | import {decimal} from "../../utils/Common" 5 | 6 | export interface OpenRequest { 7 | filledAmount: BigNumberish 8 | openPrice: BigNumberish 9 | upnlPartyA: BigNumberish 10 | upnlPartyB: BigNumberish 11 | price: BigNumberish 12 | } 13 | 14 | const limitDefaultOpenRequest: OpenRequest = { 15 | filledAmount: decimal(100n), 16 | openPrice: decimal(1n), 17 | upnlPartyA: 0, 18 | upnlPartyB: 0, 19 | price: decimal(9n, 17), 20 | } 21 | 22 | const marketDefaultOpenRequest: OpenRequest = { 23 | filledAmount: decimal(1000n), 24 | openPrice: decimal(1n), 25 | upnlPartyA: 0, 26 | upnlPartyB: 0, 27 | price: decimal(1n), 28 | } 29 | 30 | export const limitOpenRequestBuilder = () => Builder(limitDefaultOpenRequest) 31 | export const marketOpenRequestBuilder = () => Builder(marketDefaultOpenRequest) 32 | -------------------------------------------------------------------------------- /test/models/requestModels/QuoteRequest.ts: -------------------------------------------------------------------------------- 1 | import {Builder} from "builder-pattern" 2 | import {BigNumberish} from "ethers" 3 | 4 | import {SingleUpnlAndPriceSigStruct} from "../../../src/types/contracts/facets/PartyA/PartyAFacet" 5 | import {decimal, getBlockTimestamp} from "../../utils/Common" 6 | import {getDummySingleUpnlAndPriceSig} from "../../utils/SignatureUtils" 7 | import {OrderType, PositionType} from "../Enums" 8 | 9 | export interface QuoteRequest { 10 | partyBWhiteList: string[] 11 | affiliate: string 12 | symbolId: BigNumberish 13 | positionType: PositionType 14 | orderType: OrderType 15 | price: BigNumberish 16 | quantity: BigNumberish 17 | cva: BigNumberish 18 | partyAmm: BigNumberish 19 | partyBmm: BigNumberish 20 | lf: BigNumberish 21 | maxFundingRate: BigNumberish 22 | deadline: Promise | BigNumberish 23 | upnlSig: Promise 24 | } 25 | 26 | const limitDefaultQuoteRequest: QuoteRequest = { 27 | partyBWhiteList: [], 28 | symbolId: 1, 29 | positionType: PositionType.LONG, 30 | orderType: OrderType.LIMIT, 31 | price: decimal(1n), 32 | quantity: decimal(100n), 33 | cva: decimal(22n), 34 | partyAmm: decimal(75n), 35 | partyBmm: decimal(40n), 36 | lf: decimal(3n), 37 | maxFundingRate: decimal(2n, 16), 38 | deadline: getBlockTimestamp(500n), 39 | affiliate: "0xc6e7DF5E7b4f2A278906862b61205850344D4e7d", //FIXME find a better way 40 | upnlSig: getDummySingleUpnlAndPriceSig(decimal(1n)), 41 | } 42 | 43 | const marketDefaultQuoteRequest: QuoteRequest = { 44 | partyBWhiteList: [], 45 | symbolId: 1, 46 | positionType: PositionType.LONG, 47 | orderType: OrderType.MARKET, 48 | price: decimal(1n), 49 | quantity: decimal(1000n), 50 | cva: decimal(22n), 51 | partyAmm: decimal(75n), 52 | partyBmm: decimal(40n), 53 | lf: decimal(3n), 54 | maxFundingRate: decimal(2n, 16), 55 | deadline: getBlockTimestamp(500n), 56 | affiliate: "0xc6e7DF5E7b4f2A278906862b61205850344D4e7d", //FIXME find a better way 57 | upnlSig: getDummySingleUpnlAndPriceSig(decimal(1n)), 58 | } 59 | 60 | export const limitQuoteRequestBuilder = () => Builder(limitDefaultQuoteRequest) 61 | export const marketQuoteRequestBuilder = () => Builder(marketDefaultQuoteRequest) 62 | -------------------------------------------------------------------------------- /test/models/validators/AcceptCancelCloseRequestValidator.ts: -------------------------------------------------------------------------------- 1 | import {expect} from "chai" 2 | 3 | import {QuoteStructOutput} from "../../../src/types/contracts/interfaces/ISymmio" 4 | import {logger} from "../../utils/LoggerUtils" 5 | import {QuoteStatus} from "../Enums" 6 | import {Hedger} from "../Hedger" 7 | import {RunContext} from "../RunContext" 8 | import {BalanceInfo, User} from "../User" 9 | import {TransactionValidator} from "./TransactionValidator" 10 | 11 | export type AcceptCancelCloseRequestValidatorBeforeArg = { 12 | user: User 13 | hedger: Hedger 14 | quoteId: bigint 15 | } 16 | 17 | export type AcceptCancelCloseRequestValidatorBeforeOutput = { 18 | balanceInfoPartyA: BalanceInfo 19 | balanceInfoPartyB: BalanceInfo 20 | quote: QuoteStructOutput 21 | } 22 | 23 | export type AcceptCancelCloseRequestValidatorAfterArg = { 24 | user: User 25 | hedger: Hedger 26 | quoteId: bigint 27 | beforeOutput: AcceptCancelCloseRequestValidatorBeforeOutput 28 | } 29 | 30 | export class AcceptCancelCloseRequestValidator implements TransactionValidator { 31 | async before(context: RunContext, arg: AcceptCancelCloseRequestValidatorBeforeArg): Promise { 32 | logger.debug("Before AcceptCancelCloseRequestValidator...") 33 | return { 34 | balanceInfoPartyA: await arg.user.getBalanceInfo(), 35 | balanceInfoPartyB: await arg.hedger.getBalanceInfo(await arg.user.getAddress()), 36 | quote: await context.viewFacet.getQuote(arg.quoteId), 37 | } 38 | } 39 | 40 | async after(context: RunContext, arg: AcceptCancelCloseRequestValidatorAfterArg) { 41 | logger.debug("After AcceptCancelCloseRequestValidator...") 42 | // Check Quote 43 | const newQuote = await context.viewFacet.getQuote(arg.quoteId) 44 | const oldQuote = arg.beforeOutput.quote 45 | expect(newQuote.quoteStatus).to.be.equal(QuoteStatus.OPENED) 46 | expect(newQuote.quantityToClose).to.be.equal(0n) 47 | 48 | // Check Balances partyA 49 | const newBalanceInfoPartyA = await arg.user.getBalanceInfo() 50 | const oldBalanceInfoPartyA = arg.beforeOutput.balanceInfoPartyA 51 | 52 | expect(newBalanceInfoPartyA.totalPendingLockedPartyA).to.be.equal(oldBalanceInfoPartyA.totalPendingLockedPartyA.toString()) 53 | expect(newBalanceInfoPartyA.totalLockedPartyA).to.be.equal(oldBalanceInfoPartyA.totalLockedPartyA.toString()) 54 | expect(newBalanceInfoPartyA.allocatedBalances).to.be.equal(oldBalanceInfoPartyA.allocatedBalances.toString()) 55 | 56 | // Check Balances partyB 57 | const newBalanceInfoPartyB = await arg.hedger.getBalanceInfo(await arg.user.getAddress()) 58 | const oldBalanceInfoPartyB = arg.beforeOutput.balanceInfoPartyB 59 | 60 | expect(newBalanceInfoPartyB.totalPendingLockedPartyB).to.be.equal(oldBalanceInfoPartyB.totalPendingLockedPartyB.toString()) 61 | expect(newBalanceInfoPartyB.totalLockedPartyB).to.be.equal(oldBalanceInfoPartyB.totalLockedPartyB.toString()) 62 | expect(newBalanceInfoPartyB.allocatedBalances).to.be.equal(oldBalanceInfoPartyB.allocatedBalances.toString()) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /test/models/validators/AcceptCancelRequestValidator.ts: -------------------------------------------------------------------------------- 1 | import {expect} from "chai" 2 | 3 | import {QuoteStructOutput} from "../../../src/types/contracts/interfaces/ISymmio" 4 | import {getTotalPartyALockedValuesForQuotes, getTradingFeeForQuotes} from "../../utils/Common" 5 | import {logger} from "../../utils/LoggerUtils" 6 | import {expectToBeApproximately} from "../../utils/SafeMath" 7 | import {QuoteStatus} from "../Enums" 8 | import {RunContext} from "../RunContext" 9 | import {BalanceInfo, User} from "../User" 10 | import {TransactionValidator} from "./TransactionValidator" 11 | 12 | export type AcceptCancelRequestValidatorBeforeArg = { 13 | user: User 14 | quoteId: bigint 15 | } 16 | 17 | export type AcceptCancelRequestValidatorBeforeOutput = { 18 | balanceInfoPartyA: BalanceInfo 19 | quote: QuoteStructOutput 20 | } 21 | 22 | export type AcceptCancelRequestValidatorAfterArg = { 23 | user: User 24 | quoteId: bigint 25 | beforeOutput: AcceptCancelRequestValidatorBeforeOutput 26 | } 27 | 28 | export class AcceptCancelRequestValidator implements TransactionValidator { 29 | async before(context: RunContext, arg: AcceptCancelRequestValidatorBeforeArg): Promise { 30 | logger.debug("Before AcceptCancelRequestValidator...") 31 | return { 32 | balanceInfoPartyA: await arg.user.getBalanceInfo(), 33 | quote: await context.viewFacet.getQuote(arg.quoteId), 34 | } 35 | } 36 | 37 | async after(context: RunContext, arg: AcceptCancelRequestValidatorAfterArg) { 38 | logger.debug("After AcceptCancelRequestValidator...") 39 | // Check Quote 40 | const newQuote = await context.viewFacet.getQuote(arg.quoteId) 41 | const oldQuote = arg.beforeOutput.quote 42 | expect(newQuote.quoteStatus).to.be.equal(QuoteStatus.CANCELED) 43 | 44 | // Check Balances partyA 45 | const newBalanceInfoPartyA = await arg.user.getBalanceInfo() 46 | const oldBalanceInfoPartyA = arg.beforeOutput.balanceInfoPartyA 47 | 48 | const lockedValues = await getTotalPartyALockedValuesForQuotes([oldQuote]) 49 | 50 | // Assert changes in totalPendingLockedPartyA 51 | expect(newBalanceInfoPartyA.totalPendingLockedPartyA).to.equal( 52 | oldBalanceInfoPartyA.totalPendingLockedPartyA - lockedValues 53 | ) 54 | 55 | // Assert no changes in totalLockedPartyA 56 | expect(newBalanceInfoPartyA.totalLockedPartyA).to.equal( 57 | oldBalanceInfoPartyA.totalLockedPartyA 58 | ) 59 | 60 | // Calculate and assert changes in allocatedBalances 61 | const tradingFee = await getTradingFeeForQuotes(context, [arg.quoteId]) 62 | expectToBeApproximately( 63 | newBalanceInfoPartyA.allocatedBalances, 64 | oldBalanceInfoPartyA.allocatedBalances + tradingFee 65 | ) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /test/models/validators/CancelCloseRequestValidator.ts: -------------------------------------------------------------------------------- 1 | import {expect} from "chai" 2 | 3 | import {QuoteStructOutput} from "../../../src/types/contracts/interfaces/ISymmio" 4 | import {logger} from "../../utils/LoggerUtils" 5 | import {QuoteStatus} from "../Enums" 6 | import {Hedger} from "../Hedger" 7 | import {RunContext} from "../RunContext" 8 | import {BalanceInfo, User} from "../User" 9 | import {TransactionValidator} from "./TransactionValidator" 10 | 11 | export type CancelCloseRequestValidatorBeforeArg = { 12 | user: User 13 | hedger: Hedger 14 | quoteId: bigint 15 | } 16 | 17 | export type CancelCloseRequestValidatorBeforeOutput = { 18 | balanceInfoPartyA: BalanceInfo 19 | balanceInfoPartyB: BalanceInfo 20 | quote: QuoteStructOutput 21 | } 22 | 23 | export type CancelCloseRequestValidatorAfterArg = { 24 | user: User 25 | hedger: Hedger 26 | quoteId: bigint 27 | beforeOutput: CancelCloseRequestValidatorBeforeOutput 28 | } 29 | 30 | export class CancelCloseRequestValidator implements TransactionValidator { 31 | async before(context: RunContext, arg: CancelCloseRequestValidatorBeforeArg): Promise { 32 | logger.debug("Before CancelCloseRequestValidator...") 33 | return { 34 | balanceInfoPartyA: await arg.user.getBalanceInfo(), 35 | balanceInfoPartyB: await arg.hedger.getBalanceInfo(await arg.user.getAddress()), 36 | quote: await context.viewFacet.getQuote(arg.quoteId), 37 | } 38 | } 39 | 40 | async after(context: RunContext, arg: CancelCloseRequestValidatorAfterArg) { 41 | logger.debug("After CancelCloseRequestValidator...") 42 | // Check Quote 43 | const newQuote = await context.viewFacet.getQuote(arg.quoteId) 44 | const oldQuote = arg.beforeOutput.quote 45 | 46 | expect(newQuote.quoteStatus).to.be.equal(QuoteStatus.CANCEL_CLOSE_PENDING) 47 | 48 | // Check Balances partyA 49 | const newBalanceInfoPartyA = await arg.user.getBalanceInfo() 50 | const oldBalanceInfoPartyA = arg.beforeOutput.balanceInfoPartyA 51 | 52 | expect(newBalanceInfoPartyA.totalPendingLockedPartyA).to.be.equal(oldBalanceInfoPartyA.totalPendingLockedPartyA.toString()) 53 | expect(newBalanceInfoPartyA.totalLockedPartyA).to.be.equal(oldBalanceInfoPartyA.totalLockedPartyA.toString()) 54 | expect(newBalanceInfoPartyA.allocatedBalances).to.be.equal(oldBalanceInfoPartyA.allocatedBalances.toString()) 55 | 56 | // Check Balances partyB 57 | const newBalanceInfoPartyB = await arg.hedger.getBalanceInfo(await arg.user.getAddress()) 58 | const oldBalanceInfoPartyB = arg.beforeOutput.balanceInfoPartyB 59 | 60 | expect(newBalanceInfoPartyB.totalPendingLockedPartyB).to.be.equal(oldBalanceInfoPartyB.totalPendingLockedPartyB.toString()) 61 | expect(newBalanceInfoPartyB.totalLockedPartyB).to.be.equal(oldBalanceInfoPartyB.totalLockedPartyB.toString()) 62 | expect(newBalanceInfoPartyB.allocatedBalances).to.be.equal(oldBalanceInfoPartyB.allocatedBalances.toString()) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /test/models/validators/CancelQuoteValidator.ts: -------------------------------------------------------------------------------- 1 | import {expect} from "chai" 2 | 3 | import {QuoteStructOutput} from "../../../src/types/contracts/interfaces/ISymmio" 4 | import {getTotalPartyALockedValuesForQuotes, getTradingFeeForQuotes} from "../../utils/Common" 5 | import {logger} from "../../utils/LoggerUtils" 6 | import {expectToBeApproximately} from "../../utils/SafeMath" 7 | import {QuoteStatus} from "../Enums" 8 | import {RunContext} from "../RunContext" 9 | import {BalanceInfo, User} from "../User" 10 | import {TransactionValidator} from "./TransactionValidator" 11 | 12 | export type CancelQuoteValidatorBeforeArg = { 13 | user: User 14 | quoteId: bigint 15 | } 16 | 17 | export type CancelQuoteValidatorBeforeOutput = { 18 | balanceInfoPartyA: BalanceInfo 19 | quote: QuoteStructOutput 20 | } 21 | 22 | export type CancelQuoteValidatorAfterArg = { 23 | user: User 24 | quoteId: bigint 25 | targetStatus?: QuoteStatus.CANCELED | QuoteStatus.EXPIRED 26 | beforeOutput: CancelQuoteValidatorBeforeOutput 27 | } 28 | 29 | export class CancelQuoteValidator implements TransactionValidator { 30 | async before(context: RunContext, arg: CancelQuoteValidatorBeforeArg): Promise { 31 | logger.debug("Before CancelQuoteValidator...") 32 | return { 33 | balanceInfoPartyA: await arg.user.getBalanceInfo(), 34 | quote: await context.viewFacet.getQuote(arg.quoteId), 35 | } 36 | } 37 | 38 | async after(context: RunContext, arg: CancelQuoteValidatorAfterArg) { 39 | logger.debug("After CancelQuoteValidator...") 40 | // Check Quote 41 | const newQuote = await context.viewFacet.getQuote(arg.quoteId) 42 | const oldQuote = arg.beforeOutput.quote 43 | 44 | const newBalanceInfoPartyA = await arg.user.getBalanceInfo() 45 | const oldBalanceInfoPartyA = arg.beforeOutput.balanceInfoPartyA 46 | 47 | if (oldQuote.quoteStatus == BigInt(QuoteStatus.LOCKED)) { 48 | expect(newQuote.quoteStatus).to.be.equal(QuoteStatus.CANCEL_PENDING) 49 | expect(newBalanceInfoPartyA.totalPendingLockedPartyA).to.be.equal(oldBalanceInfoPartyA.totalPendingLockedPartyA.toString()) 50 | expect(newBalanceInfoPartyA.totalLockedPartyA).to.be.equal(oldBalanceInfoPartyA.totalLockedPartyA.toString()) 51 | expect(newBalanceInfoPartyA.allocatedBalances).to.be.equal(oldBalanceInfoPartyA.allocatedBalances.toString()) 52 | return 53 | } 54 | if (arg.targetStatus != null) expect(newQuote.quoteStatus).to.be.equal(arg.targetStatus) 55 | 56 | const lockedValues = await getTotalPartyALockedValuesForQuotes([oldQuote]) 57 | 58 | expect(newBalanceInfoPartyA.totalPendingLockedPartyA.toString()).to.equal((oldBalanceInfoPartyA.totalPendingLockedPartyA - lockedValues).toString()) 59 | expect(newBalanceInfoPartyA.totalLockedPartyA.toString()).to.equal(oldBalanceInfoPartyA.totalLockedPartyA.toString()) 60 | const tradingFee = await getTradingFeeForQuotes(context, [arg.quoteId]) 61 | expectToBeApproximately(BigInt(newBalanceInfoPartyA.allocatedBalances), BigInt(oldBalanceInfoPartyA.allocatedBalances) + BigInt(tradingFee)) 62 | 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /test/models/validators/CloseRequestValidator.ts: -------------------------------------------------------------------------------- 1 | import {expect} from "chai" 2 | import {QuoteStructOutput} from "../../../src/types/contracts/interfaces/ISymmio" 3 | import {logger} from "../../utils/LoggerUtils" 4 | import {QuoteStatus} from "../Enums" 5 | import {Hedger} from "../Hedger" 6 | import {RunContext} from "../RunContext" 7 | import {BalanceInfo, User} from "../User" 8 | import {TransactionValidator} from "./TransactionValidator" 9 | 10 | export type CloseRequestValidatorBeforeArg = { 11 | user: User 12 | quoteId: bigint 13 | hedger: Hedger 14 | } 15 | 16 | export type CloseRequestValidatorBeforeOutput = { 17 | balanceInfoPartyA: BalanceInfo 18 | balanceInfoPartyB: BalanceInfo 19 | quote: QuoteStructOutput 20 | } 21 | 22 | export type CloseRequestValidatorAfterArg = { 23 | user: User 24 | hedger: Hedger 25 | quoteId: bigint 26 | closePrice: bigint 27 | quantityToClose: bigint 28 | beforeOutput: CloseRequestValidatorBeforeOutput 29 | } 30 | 31 | export class CloseRequestValidator implements TransactionValidator { 32 | async before(context: RunContext, arg: CloseRequestValidatorBeforeArg): Promise { 33 | logger.debug("Before CloseRequestValidator...") 34 | return { 35 | balanceInfoPartyA: await arg.user.getBalanceInfo(), 36 | balanceInfoPartyB: await arg.hedger.getBalanceInfo(await arg.user.getAddress()), 37 | quote: await context.viewFacet.getQuote(arg.quoteId), 38 | } 39 | } 40 | 41 | async after(context: RunContext, arg: CloseRequestValidatorAfterArg) { 42 | logger.debug("After CloseRequestValidator...") 43 | // Check Quote 44 | const newQuote = await context.viewFacet.getQuote(arg.quoteId) 45 | const oldQuote = arg.beforeOutput.quote 46 | expect(newQuote.quoteStatus).to.be.equal(QuoteStatus.CLOSE_PENDING) 47 | expect(newQuote.quantityToClose).to.be.equal(arg.quantityToClose) 48 | expect(newQuote.requestedClosePrice).to.be.equal(arg.closePrice) 49 | 50 | // Check Balances partyA 51 | const newBalanceInfoPartyA = await arg.user.getBalanceInfo() 52 | const oldBalanceInfoPartyA = arg.beforeOutput.balanceInfoPartyA 53 | 54 | expect(newBalanceInfoPartyA.totalPendingLockedPartyA).to.be.equal(oldBalanceInfoPartyA.totalPendingLockedPartyA.toString()) 55 | expect(newBalanceInfoPartyA.totalLockedPartyA).to.be.equal(oldBalanceInfoPartyA.totalLockedPartyA.toString()) 56 | expect(newBalanceInfoPartyA.allocatedBalances).to.be.equal(oldBalanceInfoPartyA.allocatedBalances.toString()) 57 | 58 | // Check Balances partyB 59 | const newBalanceInfoPartyB = await arg.hedger.getBalanceInfo(await arg.user.getAddress()) 60 | const oldBalanceInfoPartyB = arg.beforeOutput.balanceInfoPartyB 61 | 62 | expect(newBalanceInfoPartyB.totalPendingLockedPartyB).to.be.equal(oldBalanceInfoPartyB.totalPendingLockedPartyB.toString()) 63 | expect(newBalanceInfoPartyB.totalLockedPartyB).to.be.equal(oldBalanceInfoPartyB.totalLockedPartyB.toString()) 64 | expect(newBalanceInfoPartyB.allocatedBalances).to.be.equal(oldBalanceInfoPartyB.allocatedBalances.toString()) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /test/models/validators/LockQuoteValidator.ts: -------------------------------------------------------------------------------- 1 | import {expect} from "chai" 2 | 3 | import {QuoteStatus} from "../Enums" 4 | import {Hedger} from "../Hedger" 5 | import {RunContext} from "../RunContext" 6 | import {BalanceInfo, User} from "../User" 7 | import {logger} from "../../utils/LoggerUtils" 8 | import {TransactionValidator} from "./TransactionValidator" 9 | 10 | export type LockQuoteValidatorBeforeArg = { 11 | user: User 12 | } 13 | 14 | export type LockQuoteValidatorBeforeOutput = { 15 | balanceInfoPartyA: BalanceInfo 16 | } 17 | 18 | export type LockQuoteValidatorAfterArg = { 19 | user: User 20 | hedger: Hedger 21 | quoteId: bigint 22 | beforeOutput: LockQuoteValidatorBeforeOutput 23 | } 24 | 25 | export class LockQuoteValidator implements TransactionValidator { 26 | async before(context: RunContext, arg: LockQuoteValidatorBeforeArg): Promise { 27 | logger.debug("Before LockQuoteValidator...") 28 | return { 29 | balanceInfoPartyA: await arg.user.getBalanceInfo(), 30 | } 31 | } 32 | 33 | async after(context: RunContext, arg: LockQuoteValidatorAfterArg) { 34 | logger.debug("After LockQuoteValidator...") 35 | const newBalanceInfo = await arg.user.getBalanceInfo() 36 | const oldBalanceInfo = arg.beforeOutput.balanceInfoPartyA 37 | 38 | expect(newBalanceInfo.totalPendingLockedPartyA).to.be.equal(oldBalanceInfo.totalPendingLockedPartyA.toString()) 39 | expect(newBalanceInfo.allocatedBalances).to.be.equal(oldBalanceInfo.allocatedBalances.toString()) 40 | const quote = await context.viewFacet.getQuote(arg.quoteId) 41 | expect(quote.quoteStatus).to.be.equal(QuoteStatus.LOCKED) 42 | expect(quote.partyB).to.be.equal(await arg.hedger.getAddress()) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test/models/validators/SendQuoteValidator.ts: -------------------------------------------------------------------------------- 1 | import {expect} from "chai" 2 | 3 | import {getTotalLockedValuesForQuoteIds, getTradingFeeForQuotes} from "../../utils/Common" 4 | import {logger} from "../../utils/LoggerUtils" 5 | import {QuoteStatus} from "../Enums" 6 | import {RunContext} from "../RunContext" 7 | import {BalanceInfo, User} from "../User" 8 | import {TransactionValidator} from "./TransactionValidator" 9 | 10 | export type SendQuoteValidatorBeforeArg = { 11 | user: User 12 | } 13 | 14 | export type SendQuoteValidatorBeforeOutput = { 15 | balanceInfoPartyA: BalanceInfo 16 | } 17 | 18 | export type SendQuoteValidatorAfterArg = { 19 | user: User 20 | quoteId: bigint 21 | beforeOutput: SendQuoteValidatorBeforeOutput 22 | } 23 | 24 | export class SendQuoteValidator implements TransactionValidator { 25 | async before(context: RunContext, arg: SendQuoteValidatorBeforeArg): Promise { 26 | logger.debug("Before SendQuoteValidator...") 27 | return { 28 | balanceInfoPartyA: await arg.user.getBalanceInfo(), 29 | } 30 | } 31 | 32 | async after(context: RunContext, arg: SendQuoteValidatorAfterArg) { 33 | logger.debug("After SendQuoteValidator...") 34 | const newBalanceInfo = await arg.user.getBalanceInfo() 35 | const oldBalanceInfo = arg.beforeOutput.balanceInfoPartyA 36 | 37 | expect(newBalanceInfo.totalPendingLockedPartyA).to.be.equal( 38 | (oldBalanceInfo.totalPendingLockedPartyA + await getTotalLockedValuesForQuoteIds(context, [arg.quoteId])).toString(), 39 | ) 40 | expect(newBalanceInfo.allocatedBalances).to.be.equal((oldBalanceInfo.allocatedBalances - await getTradingFeeForQuotes(context, [arg.quoteId]))) 41 | expect((await context.viewFacet.getQuote(arg.quoteId)).quoteStatus).to.be.equal(QuoteStatus.PENDING) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /test/models/validators/TransactionValidator.ts: -------------------------------------------------------------------------------- 1 | import {RunContext} from "../RunContext" 2 | 3 | export interface TransactionValidator { 4 | before(context: RunContext, arg: any): Promise 5 | 6 | after(context: RunContext, beforeOutput: any): Promise 7 | } 8 | -------------------------------------------------------------------------------- /test/models/validators/TransferToBridgeValidator.ts: -------------------------------------------------------------------------------- 1 | import {expect} from "chai" 2 | import {BridgeTransactionStructOutput} from "../../../src/types/contracts/interfaces/ISymmio" 3 | import {logger} from "../../utils/LoggerUtils" 4 | import {BridgeTransactionStatus} from "../Enums" 5 | import {RunContext} from "../RunContext" 6 | import {User} from "../User" 7 | import {TransactionValidator} from "./TransactionValidator" 8 | 9 | export type TransferToBridgeValidatorBeforeArg = { 10 | bridge: string 11 | user: User 12 | transactionId: bigint 13 | } 14 | 15 | export type TransferToBridgeValidatorBeforeOutput = { 16 | bridge: string 17 | depositBalancePartyA: bigint 18 | depositBalanceBridge: bigint 19 | transaction: BridgeTransactionStructOutput 20 | } 21 | 22 | export type TransferToBridgeValidatorAfterArg = { 23 | user: User 24 | amount: bigint 25 | transactionId: bigint 26 | beforeOutput: TransferToBridgeValidatorBeforeOutput 27 | } 28 | 29 | export class TransferToBridgeValidator implements TransactionValidator { 30 | async before(context: RunContext, arg: TransferToBridgeValidatorBeforeArg): Promise { 31 | logger.debug("Before TransferToBridgeValidator...") 32 | return { 33 | bridge: arg.bridge, 34 | depositBalancePartyA: await context.viewFacet.balanceOf(await arg.user.getAddress()), 35 | depositBalanceBridge: await context.viewFacet.balanceOf(arg.bridge), 36 | transaction: await context.viewFacet.getBridgeTransaction(arg.transactionId), 37 | } 38 | } 39 | 40 | async after(context: RunContext, arg: TransferToBridgeValidatorAfterArg) { 41 | logger.debug("After TransferToBridgeValidator...") 42 | 43 | // Check Transaction 44 | const transaction = await context.viewFacet.getBridgeTransaction(arg.transactionId) 45 | 46 | expect(transaction.amount).to.be.equal(arg.amount) 47 | expect(transaction.bridge).to.be.equal(arg.beforeOutput.bridge) 48 | expect(transaction.user).to.be.equal(await arg.user.getAddress()) 49 | expect(transaction.status).to.be.equal(BridgeTransactionStatus.RECEIVED) 50 | 51 | //check partyA balance 52 | const newDepositBalancePartyA = await context.viewFacet.balanceOf(await arg.user.getAddress()) 53 | expect(arg.beforeOutput.depositBalancePartyA).to.be.equal(newDepositBalancePartyA + arg.amount) 54 | 55 | //check bridge balance 56 | const newDepositBalanceBridge = await context.viewFacet.balanceOf(arg.beforeOutput.bridge) 57 | expect(arg.beforeOutput.depositBalanceBridge).to.be.equal(newDepositBalanceBridge) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /test/models/validators/UnlockQuoteValidator.ts: -------------------------------------------------------------------------------- 1 | import {expect} from "chai" 2 | 3 | import {QuoteStatus} from "../Enums" 4 | import {RunContext} from "../RunContext" 5 | import {BalanceInfo, User} from "../User" 6 | import {TransactionValidator} from "./TransactionValidator" 7 | import {logger} from "../../utils/LoggerUtils" 8 | import {ethers} from "hardhat" 9 | 10 | export type UnlockQuoteValidatorBeforeArg = { 11 | user: User 12 | } 13 | 14 | export type UnlockQuoteValidatorBeforeOutput = { 15 | balanceInfoPartyA: BalanceInfo 16 | } 17 | 18 | export type UnlockQuoteValidatorAfterArg = { 19 | user: User 20 | quoteId: bigint 21 | beforeOutput: UnlockQuoteValidatorBeforeOutput 22 | } 23 | 24 | export class UnlockQuoteValidator implements TransactionValidator { 25 | async before(context: RunContext, arg: UnlockQuoteValidatorBeforeArg): Promise { 26 | logger.debug("Before UnlockQuoteValidator...") 27 | return { 28 | balanceInfoPartyA: await arg.user.getBalanceInfo(), 29 | } 30 | } 31 | 32 | async after(context: RunContext, arg: UnlockQuoteValidatorAfterArg) { 33 | logger.debug("After UnlockQuoteValidator...") 34 | const newBalanceInfo = await arg.user.getBalanceInfo() 35 | const oldBalanceInfo = arg.beforeOutput.balanceInfoPartyA 36 | expect(newBalanceInfo.totalPendingLockedPartyA).to.be.equal(oldBalanceInfo.totalPendingLockedPartyA.toString()) 37 | expect(newBalanceInfo.allocatedBalances).to.be.equal(oldBalanceInfo.allocatedBalances.toString()) 38 | 39 | const quote = await context.viewFacet.getQuote(arg.quoteId) 40 | expect(quote.quoteStatus).to.be.equal(QuoteStatus.PENDING) 41 | expect(quote.partyB).to.be.equal(ethers.ZeroAddress) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /test/models/validators/WithdrawLockedTransactionValidator.ts: -------------------------------------------------------------------------------- 1 | import {expect} from "chai" 2 | import {BridgeTransactionStructOutput} from "../../../src/types/contracts/interfaces/ISymmio" 3 | import {logger} from "../../utils/LoggerUtils" 4 | import {BridgeTransactionStatus} from "../Enums" 5 | import {RunContext} from "../RunContext" 6 | import {TransactionValidator} from "./TransactionValidator" 7 | 8 | export type WithdrawLockedTransactionValidatorBeforeArg = { 9 | bridge: string 10 | transactionId: bigint 11 | } 12 | 13 | export type WithdrawLockedTransactionValidatorBeforeOutput = { 14 | bridge: string 15 | depositBalanceBridge: bigint 16 | transaction: BridgeTransactionStructOutput 17 | } 18 | 19 | export type WithdrawLockedTransactionValidatorAfterArg = { 20 | transactionId: bigint 21 | beforeOutput: WithdrawLockedTransactionValidatorBeforeOutput 22 | } 23 | 24 | export class WithdrawLockedTransactionValidator implements TransactionValidator { 25 | async before(context: RunContext, arg: WithdrawLockedTransactionValidatorBeforeArg): Promise { 26 | logger.debug("Before WithdrawLockedTransactionValidator...") 27 | return { 28 | bridge: arg.bridge, 29 | depositBalanceBridge: await context.viewFacet.balanceOf(arg.bridge), 30 | transaction: await context.viewFacet.getBridgeTransaction(arg.transactionId), 31 | } 32 | } 33 | 34 | async after(context: RunContext, arg: WithdrawLockedTransactionValidatorAfterArg) { 35 | logger.debug("After WithdrawLockedTransactionValidator...") 36 | 37 | // Check Transaction 38 | const transaction = await context.viewFacet.getBridgeTransaction(arg.transactionId) 39 | expect(transaction.status).to.be.equal(BridgeTransactionStatus.WITHDRAWN) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /test/utils/LoggerUtils.ts: -------------------------------------------------------------------------------- 1 | import winston, {format, transports} from "winston" 2 | 3 | const customLevels = { 4 | levels: { 5 | error: 0, 6 | warning: 1, 7 | contractLogs: 2, 8 | info: 3, 9 | debug: 4, 10 | detailedDebug: 5, 11 | detailedEventDebug: 6, 12 | }, 13 | colors: { 14 | error: "red", 15 | warning: "yellow", 16 | info: "green", 17 | contractLogs: "green", 18 | debug: "blue", 19 | detailedDebug: "cyan", 20 | detailedEventDebug: "cyan", 21 | }, 22 | } 23 | 24 | let logLevel = process.env.LOG_LEVEL 25 | if (logLevel == null) logLevel = "info" 26 | if (process.env.TEST_MODE == "static") logLevel = "error" 27 | let conf 28 | 29 | const detailedDebugTransport = new transports.File({ 30 | filename: "detailedDebug.log", 31 | level: "detailedDebug", 32 | format: format.combine(format.colorize(), format.timestamp(), format.prettyPrint()), 33 | }) 34 | 35 | switch (logLevel) { 36 | case "detailedEventDebug": 37 | conf = { 38 | level: "detailedEventDebug", 39 | format: format.combine(format.colorize(), format.timestamp(), format.prettyPrint()), 40 | } 41 | break 42 | case "detailedDebug": 43 | conf = { 44 | level: "detailedDebug", 45 | format: format.combine(format.colorize(), format.timestamp(), format.prettyPrint()), 46 | } 47 | break 48 | case "debug": 49 | conf = { 50 | level: "debug", 51 | format: format.combine( 52 | format.colorize(), 53 | format.timestamp(), 54 | format.printf(({level, message, timestamp}) => { 55 | return `${timestamp} ${level}: ${message}` 56 | }), 57 | ), 58 | } 59 | break 60 | case "contractLogs": 61 | conf = { 62 | level: "contractLogs", 63 | format: format.combine( 64 | format.colorize(), 65 | format.timestamp(), 66 | format.printf(({level, message, timestamp}) => { 67 | return `${timestamp} ${level}: ${message}` 68 | }), 69 | ), 70 | } 71 | break 72 | case "info": 73 | conf = { 74 | level: "info", 75 | format: format.combine( 76 | format.colorize(), 77 | format.timestamp(), 78 | format.printf(({level, message, timestamp}) => { 79 | return `${timestamp} ${level}: ${message}` 80 | }), 81 | ), 82 | } 83 | break 84 | case "error": 85 | conf = { 86 | level: "error", 87 | format: format.combine( 88 | format.colorize(), 89 | format.timestamp(), 90 | format.printf(({level, message, timestamp}) => { 91 | return `${timestamp} ${level}: ${message}` 92 | }), 93 | ), 94 | } 95 | break 96 | case "warning": 97 | conf = { 98 | level: "warning", 99 | format: format.combine( 100 | format.colorize(), 101 | format.timestamp(), 102 | format.printf(({level, message, timestamp}) => { 103 | return `${timestamp} ${level}: ${message}` 104 | }), 105 | ), 106 | } 107 | break 108 | } 109 | 110 | export const logger: any = winston.createLogger({ 111 | levels: customLevels.levels, 112 | transports: [new winston.transports.Console(conf), detailedDebugTransport], 113 | }) 114 | winston.addColors(customLevels.colors) 115 | -------------------------------------------------------------------------------- /test/utils/Pauser.ts: -------------------------------------------------------------------------------- 1 | import {BehaviorSubject, Observable} from "rxjs" 2 | 3 | export function pause(pauser: BehaviorSubject) { 4 | return (source: Observable) => 5 | new Observable(observer => { 6 | let buffer: any[] = [] 7 | 8 | const sourceSubscription = source.subscribe({ 9 | next(value) { 10 | if (!pauser.value) { 11 | observer.next(value) 12 | } else { 13 | buffer.push(value) 14 | } 15 | }, 16 | error(err) { 17 | observer.error(err) 18 | }, 19 | complete() { 20 | observer.complete() 21 | }, 22 | }) 23 | 24 | const bufferSubscription = pauser.subscribe(pause => { 25 | if (!pause) { 26 | buffer.forEach(value => observer.next(value)) 27 | buffer = [] 28 | } 29 | }) 30 | 31 | return () => { 32 | sourceSubscription.unsubscribe() 33 | bufferSubscription.unsubscribe() 34 | } 35 | }) 36 | } 37 | -------------------------------------------------------------------------------- /test/utils/PriceUtils.ts: -------------------------------------------------------------------------------- 1 | import {QuoteStructOutput} from "../../src/types/contracts/interfaces/ISymmio" 2 | import {decimal} from "./Common" 3 | import {randomBigNumber} from "./RandomUtils" 4 | 5 | export async function getPrice(): Promise { 6 | const def = 200000n * 10n ** 18n 7 | if (process.env.TEST_MODE !== "fuzz") return def 8 | return randomBigNumber(110000000000000000000n, 100000000000000000000n) 9 | } 10 | 11 | export function calculateExpectedClosePriceForForceClose(q: QuoteStructOutput, penalty: bigint, isLongPosition: boolean): bigint { 12 | const a = (q.requestedClosePrice * penalty) / decimal(1n) 13 | return isLongPosition ? q.requestedClosePrice + a : q.requestedClosePrice - a 14 | } 15 | 16 | export function calculateExpectedAvgPriceForForceClose(q: QuoteStructOutput, expectedClosePrice: bigint): bigint { 17 | return ((q.avgClosedPrice * q.closedAmount) + (q.quantityToClose * expectedClosePrice)) / (q.closedAmount + q.quantityToClose) 18 | } 19 | -------------------------------------------------------------------------------- /test/utils/RandomUtils.ts: -------------------------------------------------------------------------------- 1 | import {decimal, unDecimal} from "./Common" 2 | import {ethers} from "ethers" 3 | 4 | export function pick(array: any[]): any { 5 | return array[Math.floor(Math.random() * array.length)] 6 | } 7 | 8 | export function randomBigNumber(max: bigint, min?: bigint): bigint { 9 | if (min == null) return BigInt(ethers.randomBytes(32).toString()) % max 10 | const diff = max - min 11 | return min + randomBigNumber(diff) 12 | } 13 | 14 | export function randomBigNumberRatio(value: bigint, max: number, min?: number): bigint { 15 | return unDecimal( 16 | value * randomBigNumber(decimal(BigInt(Math.floor(max * 10000)), 14), min != null ? decimal(BigInt(Math.floor(min * 10000)), 14) : undefined), 17 | ) 18 | } 19 | 20 | -------------------------------------------------------------------------------- /test/utils/SafeMath.ts: -------------------------------------------------------------------------------- 1 | import {expect} from "chai" 2 | import {BigNumber as BN} from "bignumber.js" 3 | 4 | export function safeDiv(a: bigint, b: bigint): bigint { 5 | const value = new BN(a.toString()).dividedBy(new BN(b.toString())) 6 | if (value.isLessThan(1) && value.isGreaterThan(0)) { 7 | throw new Error("Division led to fraction!") 8 | } 9 | return BigInt(value.toFixed(0)) 10 | } 11 | 12 | BN.set({ROUNDING_MODE: BN.ROUND_CEIL}) 13 | 14 | export function roundToPrecision(a: bigint, precision: number): bigint { 15 | return BigInt( 16 | new BN(a.toString()) 17 | .dividedBy(new BN(10).pow(18)) 18 | .toFixed(precision) 19 | ) * 10n ** 18n 20 | } 21 | 22 | export function expectToBeApproximately(a: bigint, b: bigint): void { 23 | expect(b - a).to.be.lte(10n) 24 | } 25 | -------------------------------------------------------------------------------- /test/utils/TxUtils.ts: -------------------------------------------------------------------------------- 1 | export async function runTx(prm: Promise): Promise { 2 | return await (await prm).wait() 3 | } 4 | -------------------------------------------------------------------------------- /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 | "noErrorTruncation": true 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /utils/clean.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | rm -rf artifacts 4 | rm -rf cache 5 | rm -rf src 6 | -------------------------------------------------------------------------------- /utils/deploy-local.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | npx hardhat run scripts/Initialize.ts --network localhost 3 | -------------------------------------------------------------------------------- /utils/fuzz-tests: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | ./docker/compile.sh 3 | TEST_MODE=fuzz npx hardhat test 4 | -------------------------------------------------------------------------------- /utils/get_selectors.py: -------------------------------------------------------------------------------- 1 | import json 2 | from eth_hash.auto import keccak 3 | import json 4 | 5 | def get_function_selector(function_name, inputs): 6 | """ 7 | Generate the function selector (first 4 bytes of keccak256 hash) for a function signature 8 | """ 9 | # Create the function signature 10 | types = [input_['type'] for input_ in inputs] 11 | signature = f"{function_name}({','.join(types)})" 12 | 13 | # Get keccak256 hash and take first 4 bytes 14 | selector = keccak(signature.encode())[:4].hex() 15 | return selector, signature 16 | 17 | def process_abi(abi_json): 18 | """ 19 | Process ABI and return all function selectors 20 | """ 21 | selectors = {} 22 | 23 | # Filter for function entries 24 | functions = [entry for entry in abi_json if entry.get('type') == 'function'] 25 | 26 | # Generate selectors for each function 27 | for func in functions: 28 | name = func['name'] 29 | inputs = func.get('inputs', []) 30 | selector, signature = get_function_selector(name, inputs) 31 | selectors[selector] = { 32 | 'name': name, 33 | 'signature': signature, 34 | 'inputs': [input_['type'] for input_ in inputs], 35 | 'stateMutability': func.get('stateMutability', '') 36 | } 37 | 38 | return selectors 39 | 40 | def main(): 41 | # Read ABI from file 42 | with open('abis/symmio.json', 'r') as f: 43 | abi = json.load(f) 44 | 45 | # Get selectors 46 | selectors = process_abi(abi) 47 | 48 | # Print results in a formatted way 49 | print(f"Found {len(selectors)} function selectors:\n") 50 | for selector, data in sorted(selectors.items(), key=lambda x: x[1]['name'].lower()): 51 | print(f"0x{selector} - {data['name']}") 52 | print(f" Signature: {data['signature']}") 53 | print() 54 | 55 | if __name__ == "__main__": 56 | main() -------------------------------------------------------------------------------- /utils/get_symbol_details_config_setup.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from multicallable import Multicallable 4 | from web3 import Web3 5 | 6 | rpc = "" 7 | max_symbol_id = 1000 8 | contract_address = "" 9 | 10 | w3 = Web3(Web3.HTTPProvider(rpc)) 11 | contract = Multicallable(w3.to_checksum_address(contract_address), json.loads('''[ 12 | { 13 | "inputs": [ 14 | { 15 | "internalType": "uint256", 16 | "name": "start", 17 | "type": "uint256" 18 | }, 19 | { 20 | "internalType": "uint256", 21 | "name": "size", 22 | "type": "uint256" 23 | } 24 | ], 25 | "name": "getSymbols", 26 | "outputs": [ 27 | { 28 | "components": [ 29 | {"internalType": "uint256", "name": "symbolId", "type": "uint256"}, 30 | {"internalType": "string", "name": "name", "type": "string"}, 31 | {"internalType": "bool", "name": "isValid", "type": "bool"}, 32 | {"internalType": "uint256", "name": "minAcceptableQuoteValue", "type": "uint256"}, 33 | {"internalType": "uint256", "name": "minAcceptablePortionLF", "type": "uint256"}, 34 | {"internalType": "uint256", "name": "tradingFee", "type": "uint256"}, 35 | {"internalType": "uint256", "name": "maxLeverage", "type": "uint256"}, 36 | {"internalType": "uint256", "name": "fundingRateEpochDuration", "type": "uint256"}, 37 | {"internalType": "uint256", "name": "fundingRateWindowTime", "type": "uint256"} 38 | ], 39 | "internalType": "struct Symbol[]", 40 | "name": "", 41 | "type": "tuple[]" 42 | } 43 | ], 44 | "stateMutability": "view", 45 | "type": "function" 46 | }, 47 | { 48 | "inputs": [ 49 | { 50 | "internalType": "uint256", 51 | "name": "symbolId", 52 | "type": "uint256" 53 | } 54 | ], 55 | "name": "forceCloseGapRatio", 56 | "outputs": [ 57 | { 58 | "internalType": "uint256", 59 | "name": "", 60 | "type": "uint256" 61 | } 62 | ], 63 | "stateMutability": "view", 64 | "type": "function" 65 | } 66 | ]'''), w3) 67 | 68 | symbols = contract.getSymbols([(0, max_symbol_id)]).call(n=max_symbol_id // 200 + 1, progress_bar=True)[0] 69 | 70 | symbols_json = [] 71 | symbol_names = [] 72 | symbol_ids = [symbol[0] for symbol in symbols] 73 | 74 | force_close_gap_ratios = contract.forceCloseGapRatio(symbol_ids).call(n=len(symbols) // 200 + 1, progress_bar=True) 75 | for symbol, force_close_data in zip(symbols, force_close_gap_ratios): 76 | if not symbol[2] or symbol[1].endswith("BYBIT") or symbol[1] in symbol_names: 77 | continue 78 | symbol_names.append(symbol[1]) 79 | symbols_json.append({ 80 | "symbolId": symbol[0], 81 | "name": symbol[1], 82 | "isValid": symbol[2], 83 | "minAcceptableQuoteValue": symbol[3], 84 | "minAcceptablePortionLF": symbol[4], 85 | "tradingFee": 800000000000000 if symbol[1] not in ["BTCUSDT", "ETHUSDT"] else 600000000000000, 86 | "maxLeverage": symbol[6], 87 | "fundingRateEpochDuration": symbol[7], 88 | "fundingRateWindowTime": symbol[8], 89 | "forceCloseGapRatio": force_close_data 90 | }) 91 | 92 | print(json.dumps(symbols_json, indent=2)) 93 | -------------------------------------------------------------------------------- /utils/remove_duplicate_events.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | 5 | def modify_backward_compatibility_lines(file_path: str, flag: int) -> None: 6 | """Modify lines in a file that have the backward compatibility comment. 7 | 8 | Args: 9 | file_path: The path to the file to be modified. 10 | flag: 11 | - If 1, comment out lines. 12 | - If 0, uncomment lines. 13 | - If 2, remove lines ending with the target comment. 14 | """ 15 | target_comment = "// For backward compatibility, will be removed in future" 16 | 17 | with open(file_path, "r", encoding="utf-8") as file: 18 | lines = file.readlines() 19 | 20 | modified_lines = [] 21 | 22 | for line in lines: 23 | stripped_line = line.rstrip() 24 | if stripped_line.endswith(target_comment): 25 | # Line ends with target comment 26 | if flag == 1: 27 | # Comment out the line if it's not already commented 28 | if not line.lstrip().startswith("//"): 29 | modified_lines.append("// " + line) 30 | else: 31 | # Already commented, keep as is 32 | modified_lines.append(line) 33 | elif flag == 0: 34 | # Uncomment the line if it is commented 35 | if line.lstrip().startswith("//"): 36 | # Find the position of the first occurrence of "//" 37 | comment_index = line.find("//") 38 | # Remove the leading "// " from the line 39 | modified_line = line[:comment_index] + line[comment_index + 3:] 40 | modified_lines.append(modified_line) 41 | else: 42 | # Not commented, keep as is 43 | modified_lines.append(line) 44 | elif flag == 2: 45 | # Remove the line entirely (do not add it to modified_lines) 46 | continue 47 | else: 48 | # Invalid flag, keep the line as is 49 | modified_lines.append(line) 50 | else: 51 | # Line does not end with target comment, keep as is 52 | modified_lines.append(line) 53 | 54 | with open(file_path, "w", encoding="utf-8") as file: 55 | file.writelines(modified_lines) 56 | 57 | print(f"Updated file saved to {file_path}") 58 | 59 | 60 | def process_directory(directory_path: str, flag: int) -> None: 61 | """Process all files in the given directory and its subdirectories. 62 | 63 | Args: 64 | directory_path: The path to the directory containing the files to be modified. 65 | flag: 66 | - If 1, comment out lines. 67 | - If 0, uncomment lines. 68 | - If 2, remove lines ending with the target comment. 69 | """ 70 | for root, dirs, files in os.walk(directory_path): 71 | for filename in files: 72 | # Process only Solidity files 73 | if filename.endswith('.sol'): 74 | file_path = os.path.join(root, filename) 75 | if os.path.isfile(file_path): 76 | modify_backward_compatibility_lines(file_path, flag) 77 | 78 | 79 | if __name__ == "__main__": 80 | if len(sys.argv) != 2: 81 | print("Usage: python update_backward_compatibility.py ") 82 | print("Flags:") 83 | print(" 1 - Comment out lines ending with the target comment.") 84 | print(" 0 - Uncomment lines ending with the target comment.") 85 | print(" 2 - Remove lines ending with the target comment.") 86 | sys.exit(1) 87 | 88 | flag = int(sys.argv[1]) 89 | directory_path = "./contracts" 90 | 91 | process_directory(directory_path, flag) 92 | -------------------------------------------------------------------------------- /utils/runTests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | python3 utils/update_sig_checks.py 1 4 | npx hardhat test 5 | python3 utils/update_sig_checks.py 0 -------------------------------------------------------------------------------- /utils/runTestsWithLocalNode.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | fuser -k 8545/tcp 4 | fuser -k 8090/tcp 5 | 6 | # Replace these with your commands 7 | command1="npx hardhat node" 8 | command2="npx hardhat run scripts/Initialize.ts --network localhost" 9 | command3=". ../v3-hedger/.venv/bin/activate" 10 | command4="../v3-hedger/runner ../v3-hedger/server_runner.py" 11 | command5="npx hardhat test --network localhost" 12 | 13 | # Log file path 14 | logfile="output.log" 15 | serverLogfile="serverLog.log" 16 | detailedDebugFile="detailedDebug.log" 17 | 18 | # Clear the log files 19 | truncate -s 0 $logfile 20 | truncate -s 0 $detailedDebugFile 21 | truncate -s 0 $serverLogfile 22 | 23 | echo "Running hardhat node" 24 | eval "$command1" >/dev/null 2>&1 & 25 | 26 | sleep 3 27 | 28 | echo "Initializing..." 29 | eval "$command2" 2>&1 | tee -a $logfile 30 | 31 | # Activate the Python virtual environment 32 | $command3 33 | 34 | echo "Running hedger server" 35 | eval "$command4" 2>&1 > $serverLogfile & 36 | 37 | sleep 6 38 | 39 | echo "Running Fuzz tests..." 40 | eval "$command5" 2>&1 | tee -a $logfile 41 | -------------------------------------------------------------------------------- /utils/static-tests: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | ./docker/compile.sh 3 | TEST_MODE=static npx hardhat test 4 | -------------------------------------------------------------------------------- /utils/update_sig_checks.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | 5 | def modify_signature_checks(file_path: str, flag: int) -> None: 6 | """Modify signature checks in a file based on the flag. 7 | 8 | Args: 9 | file_path: The path to the file to be modified. 10 | flag: If 1, comment out signature checks. If 0, uncomment them. 11 | """ 12 | with open(file_path, "r", encoding="utf-8") as file: 13 | lines = file.readlines() 14 | 15 | inside_signature_check = False 16 | modified_lines = [] 17 | 18 | for line in lines: 19 | if "// == SignatureCheck( ==" in line: 20 | inside_signature_check = True 21 | modified_lines.append(line) # Keep the start marker line as it is 22 | continue 23 | 24 | if "// == ) ==" in line: 25 | inside_signature_check = False 26 | modified_lines.append(line) # Keep the end marker line as it is 27 | continue 28 | 29 | if inside_signature_check: 30 | if flag == 1 and not line.strip().startswith("//"): 31 | # Comment out the line 32 | modified_lines.append("// " + line) 33 | elif flag == 0 and line.strip().startswith("//"): 34 | # Uncomment the line by removing "// " 35 | modified_lines.append(line[line.index("// ") + 3:]) 36 | else: 37 | modified_lines.append(line) 38 | else: 39 | modified_lines.append(line) 40 | 41 | with open(file_path, "w", encoding="utf-8") as file: 42 | file.writelines(modified_lines) 43 | 44 | print(f"Updated file saved to {file_path}") 45 | 46 | 47 | def process_directory(directory_path: str, flag: int) -> None: 48 | """Process all files in the given directory. 49 | 50 | Args: 51 | directory_path: The path to the directory containing the files to be modified. 52 | flag: If 1, comment out signature checks. If 0, uncomment them. 53 | """ 54 | for filename in os.listdir(directory_path): 55 | file_path = os.path.join(directory_path, filename) 56 | if os.path.isfile(file_path): 57 | modify_signature_checks(file_path, flag) 58 | 59 | 60 | if __name__ == "__main__": 61 | if len(sys.argv) != 2: 62 | print("Usage: python update_sig_checks.py ") 63 | sys.exit(1) 64 | 65 | flag = int(sys.argv[1]) 66 | directory_path = "./contracts/libraries/muon" 67 | 68 | process_directory(directory_path, flag) 69 | --------------------------------------------------------------------------------