diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..5ec5af5 --- /dev/null +++ b/.env.example @@ -0,0 +1,50 @@ +FUJI_CHAIN_ID="43113" +ORDERLY_CHAIN_ID="986532" +ORDERLYOP_CHAIN_ID="4460" +ORDERLYMAIN_CHAIN_ID="291" +ARBITRUMGOERLI_CHAIN_ID="421613" +ARBITRUM_CHAIN_ID="42161" +MUMBAI_CHAIN_ID="80001" + +FUJI_LZ_CHAIN_ID="10106" +ORDERLY_LZ_CHAIN_ID="10174" +ORDERLYOP_LZ_CHAIN_ID="10200" +ARBITRUMGOERLI_LZ_CHAIN_ID="10143" + +RPC_URL_ETHEREUM="https://rpc.ankr.com/eth" +RPC_URL_GOERLI="https://rpc.ankr.com/eth_goerli" +RPC_URL_HYPERSPACE="https://rpc.ankr.com/filecoin_testnet" +RPC_URL_SEPOLIA="https://rpc.sepolia.org" +RPC_URL_FUJI="https://api.avax-test.network/ext/bc/C/rpc" +RPC_URL_ORDERLY="https://testnet-fuji-rpc-1.orderly.network/ext/bc/fVgSf4ruGhwvEMd8z6dRwsH6XgRaq31wxN4tRhZPN6rWYhjVt/rpc" +RPC_URL_MUMBAI="https://polygon-mumbai.infura.io/v3/eda7ae04091a465eb960308cb44bf0b3" +RPC_URL_BSC="https://bsc-dataseed1.binance.org" +RPC_URL_ARBITRUMGOERLI="https://goerli-rollup.arbitrum.io/rpc" +RPC_URL_ARBITRUM="https://arb1.arbitrum.io/rpc" +RPC_URL_OPGOERLI="https://optimism-goerli.blockpi.network/v1/rpc/public" +RPC_URL_OPTIMISM="https://optimism.publicnode.com" +RPC_URL_ORDERLYOP="https://l2-orderly-l2-4460-sepolia-8tc3sd7dvy.t.conduit.xyz" +RPC_URL_ORDERLYMAIN="https://rpc.orderly.network" + +ETHEREUM_ENDPOINT="0x66A71Dcef29A0fFBDBE3c6a460a3B5BC225Cd675" +BSC_ENDPOINT="0x3c2269811836af69497E5F486A85D7316753cf62" +AVALANCHE_ENDPOINT="0x3c2269811836af69497E5F486A85D7316753cf62" +POLYGON_ENDPOINT="0x3c2269811836af69497E5F486A85D7316753cf62" +ARBITRUM_ENDPOINT="0x3c2269811836af69497E5F486A85D7316753cf62" +OPTIMISM_ENDPOINT="0x3c2269811836af69497E5F486A85D7316753cf62" +FANTOM_ENDPOINT="0xb6319cC6c8c27A8F5dAF0dD3DF91EA35C4720dd7" +ORDERLY_ENDPOINT="0xae92d5aD7583AD66E49A0c67BAd18F6ba52dDDc1" +GOERLI_ENDPOINT="0xbfD2135BFfbb0B5378b56643c2Df8a87552Bfa23" +BSC_TESTNET_ENDPOINT="0x6Fcb97553D41516Cb228ac03FdC8B9a0a9df04A1" +FUJI_ENDPOINT="0x93f54D755A063cE7bB9e6Ac47Eccc8e33411d706" +MUMBAI_ENDPOINT="0xf69186dfBa60DdB133E91E9A4B5673624293d8F8" +ARBITRUMGOERLI_ENDPOINT="0x6aB5Ae6822647046626e83ee6dB8187151E1d5ab" +ORDERLYOP_ENDPOINT="0x83c73Da98cf733B03315aFa8758834b36a195b87" + +ORDERLY_PRIVATE_KEY="0x1" +VAULT_CROSS_CHAIN_MANAGER_ADDRESS="0x1" +LEDGER_CROSS_CHAIN_MANAGER_ADDRESS="0x1" +ETHERSCAN_KEY="0x1" +ETHERSCAN_API_KEY="" +ARB_ETHERSCAN_API_KEY="" +OP_ETHERSCAN_API_KEY="" \ No newline at end of file diff --git a/.gitignore b/.gitignore index 7d17b64..a74b8b2 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,10 @@ docs/ node_modules # artifacts -src/**/artifacts/ \ No newline at end of file +src/**/artifacts/ + +broadcast/ + +*.zip + +.DS_Store \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..e8421aa --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,16 @@ +stages: + - test + +test-job: + stage: test + image: ghcr.io/foundry-rs/foundry + rules: + - if: $CI_PIPELINE_SOURCE == 'merge_request_event' + before_script: + - git config --global url."https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/".insteadOf "git@gitlab.com:" + script: + - echo "Install build essentials" + - apk add --no-cache openssh + - echo "Build with commit $CI_COMMIT_BRANCH!" + - forge install + - forge test diff --git a/.gitmodules b/.gitmodules index 15bb44a..fe8bf8f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,13 +2,10 @@ path = lib/forge-std url = https://github.com/foundry-rs/forge-std branch = v1.5.0 -[submodule "lib/solmate"] - path = lib/solmate - url = https://github.com/transmissions11/solmate -[submodule "lib/create3-factory"] - path = lib/create3-factory - url = https://github.com/zeframlou/create3-factory [submodule "lib/openzeppelin-contracts"] path = lib/openzeppelin-contracts url = https://github.com/openzeppelin/openzeppelin-contracts branch = v4.8.2 +[submodule "lib/openzeppelin-contracts-upgradeable"] + path = lib/openzeppelin-contracts-upgradeable + url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..51fbf5b --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2023 Xiaohui Zhao + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index dcbd338..7ad3070 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,10 @@ -# poc +# Introduction + +This repo is built with Foundry, including the scripts for test and deployment. + +The major content is about the contracts for Orderly V2, including the Ledger and Vault contracts. + +A submodule inside `lib` folder is used for the contracts of Cross-Chain, named **cross-chain-relay**. For the information of this submodule, please refer to [cross-chain-relay](https://gitlab.com/orderlynetwork/orderly-v2/evm-cross-chain) ## Usage @@ -8,6 +14,12 @@ Install dependencies forge install ``` +Update submodule + +```sh +git submodule update +``` + Build ```sh @@ -19,3 +31,256 @@ Test ```sh forge test -vvvv ``` + +# Contract overview + +## Layout + +All source code for contracts are inside `src` folder, the script for test is inside `test` folder, the script for deployment is inside `script` folder. + +For more information of project structure, please see standard [foundry project layout](https://book.getfoundry.sh/projects/project-layout) + +## Src + +1. dataLayout + + Complicated dataLayout, or slot storage for contract. The most important data structure is the `userLedger` defined in `LedgerDataLayout.sol`, which is the mapping from accountId (`bytes32` type) to the type `AccountTypes.Account`. + +2. interface + + Interface defines for all contracts, including the events, the errors, and the signature of functions. + +3. library + + Libraries defines data structure and inline functions. + + 1. types + + Define data structure for other contracts, the most import data structure including the `Account` type, `Event` type, `CrossChainMessage` type, etc. + + 2. typesHelper + + Helper functions to do different operations on types, the contract use these helper functions with: `using typesHelper for types` + + 3. other libraries + + Libraries of inline functions, the `Signature.sol` is used by Ledger contract for the verification of signature from Engine for event upload and trades upload, the `Utils.sol` is used by Vault contract to compute the account id of an Orderly user. + +4. vaultSide + + Contracts for `Vault`, including the Vault contract, and a test version of USDC contract. Vault contract is deployed on some EVM-compatiable chain that Orderly supported (e.g. Arbitrum-Goerli), it works as an vault for the user's assets. Users can deposit to or withdraw from Vault contract on some chain they choose. + +5. Remaining contracts + + The most important contracts for `settlement layer`, or in another word, the Ledger side. They are `Ledger`, `OperatorManager`, `FeeManager`, `MarketManager`, `VaultManager`. All these contracts are deployed on the Orderly L2 based OP Stack to provide the settlement service for Orderly users. + + On Ledger side, the `Ledger` contract is the main contract to store the user's account information and execute actions according to the function call from `OperatorManager`, and the `OperatorManager` contract is used to receive the operation request from Engine, the `FeeManager` contract is used to manage the fee collector address, the `MarketManager` contract is used to manage the market information for trading context, the `VaultManager` contract is used to manage the token balance of the Vault contract on each EVM chain. + + Check [conflunce here](https://wootraders.atlassian.net/wiki/spaces/ORDER/pages/279838766/Solidity+Contract+Overview) for more info + +# Contract deploy + +To deploy/upgrade the contracts on Vault and Ledger side, the scripts version 2 under folder `script` is used. The scripts version 1 is deprecated. + +Before deployment/upgrading, a suitable `.env` file is needed to be created under the root folder of this repo. The `.env` file should contain the following information: + +- RPC URL for each chain, such as RPC_URL_ORDERLYOP, RPC_URL_ARBITRUMGOERLI, etc. +- Private key for the deployer account, such as the ORDERLY_PRIVATE_KEY, ARBITRUM_PRIVATE_KEY, etc. +- Related deployed contract address, such as VAULT_CROSS_CHAIN_MANAGER_ADDRESS, LEDGER_CROSS_CHAIN_MANAGER_ADDRESS, etc. + +## Contract information board + +The information about deployed contract adress and abi files for each environment is stored in the confluence page: +https://wootraders.atlassian.net/wiki/spaces/ORDER/pages/343441906/Orderly+V2+Contract+Information+Board + +## Ledger scripts + +### Deploy command: + +The contracts on Ledger side is deployed on Orderly L2 based OP Stack, so the rpc is set as RPC_URL_ORDERLYOP. The deploy command is as follows: + +```shell +# orderly testnet +forge script script/ledgerV2/DeployProxyLedger.s.sol -f $RPC_URL_ORDERLYOP --broadcast --verifier-url https://testnet-explorer.orderly.org/api\? --verifier blockscout --verify +# orderly mainnet +forge script script/ledgerV2/DeployProxyLedger.s.sol -f $RPC_URL_ORDERLYMAIN --broadcast --verifier-url https://explorer.orderly.network/api\? --verifier blockscout --verify +``` + +After executing the command, the deployed contracts are Ledger, OperatorManager, FeeManager, MarketManager, and VaultManager. The CrossChainManager is deployed through another repo as mentioned above. + +The addresses of the deployed contracts will be listed inside `config` folder, named as `deploy-ledger.json` file. + +### Set Cross-Chain Manager + +Once the contracts on Ledger side are deployed, the Cross-Chain Manager should be set for Leder contract. The command to set Cross-Chain Manager is as follows: + +```shell +# orderly testnet +forge script script/ledgerV2/SetCrossChainManager.s.sol -f $RPC_URL_ORDERLYOP --broadcast +# orderly mainnet +forge script script/ledgerV2/SetCrossChainManager.s.sol -f $RPC_URL_ORDERLYMAIN --broadcast +``` + +### Upgrade command: + +Transparent upgrade pattern is used for contracts on Ledger side, to upgrade a specific contract, the corresponding upgrade script should be executed. The upgrade command is as follows: + +```shell +# orderly testnet +forge script script/ledgerV2/UpgradeLedger.s.sol -f $RPC_URL_ORDERLYOP --broadcast --verifier-url https://testnet-explorer.orderly.org/api\? --verifier blockscout --verify +forge script script/ledgerV2/UpgradeOperatorManager.s.sol -f $RPC_URL_ORDERLYOP --broadcast --verifier-url https://testnet-explorer.orderly.org/api\? --verifier blockscout --verify +forge script script/ledgerV2/UpgradeFeeManager.s.sol -f $RPC_URL_ORDERLYOP --broadcast --verifier-url https://testnet-explorer.orderly.org/api\? --verifier blockscout --verify +forge script script/ledgerV2/UpgradeVaultManager.s.sol -f $RPC_URL_ORDERLYOP --broadcast --verifier-url https://testnet-explorer.orderly.org/api\? --verifier blockscout --verify +forge script script/ledgerV2/UpgradeMarketManager.s.sol -f $RPC_URL_ORDERLYOP --broadcast --verifier-url https://testnet-explorer.orderly.org/api\? --verifier blockscout --verify +# orderly mainnet +forge script script/ledgerV2/UpgradeLedger.s.sol -f $RPC_URL_ORDERLYMAIN --broadcast --verifier-url https://explorer.orderly.network/api\? --verifier blockscout --verify +forge script script/ledgerV2/UpgradeOperatorManager.s.sol -f $RPC_URL_ORDERLYMAIN --broadcast --verifier-url https://explorer.orderly.network/api\? --verifier blockscout --verify +forge script script/ledgerV2/UpgradeFeeManager.s.sol -f $RPC_URL_ORDERLYMAIN --broadcast --verifier-url https://explorer.orderly.network/api\? --verifier blockscout --verify +forge script script/ledgerV2/UpgradeVaultManager.s.sol -f $RPC_URL_ORDERLYMAIN --broadcast --verifier-url https://explorer.orderly.network/api\? --verifier blockscout --verify +forge script script/ledgerV2/UpgradeMarketManager.s.sol -f $RPC_URL_ORDERLYMAIN --broadcast --verifier-url https://explorer.orderly.network/api\? --verifier blockscout --verify +``` + +### Deploy new implement command: + +```shell +# orderly testnet +forge script script/ledgerV2/DeployNewLedger.s.sol -f $RPC_URL_ORDERLYOP --broadcast --verifier-url https://testnet-explorer.orderly.org/api\? --verifier blockscout --verify +forge script script/ledgerV2/DeployNewOperatorManager.s.sol -f $RPC_URL_ORDERLYOP --broadcast --verifier-url https://testnet-explorer.orderly.org/api\? --verifier blockscout --verify +forge script script/ledgerV2/DeployNewFeeManager.s.sol -f $RPC_URL_ORDERLYOP --broadcast --verifier-url https://testnet-explorer.orderly.org/api\? --verifier blockscout --verify +forge script script/ledgerV2/DeployNewVaultManager.s.sol -f $RPC_URL_ORDERLYOP --broadcast --verifier-url https://testnet-explorer.orderly.org/api\? --verifier blockscout --verify +forge script script/ledgerV2/DeployNewMarketManager.s.sol -f $RPC_URL_ORDERLYOP --broadcast --verifier-url https://testnet-explorer.orderly.org/api\? --verifier blockscout --verify +forge script script/ledgerV2/DeployNewLedgerImplA.s.sol -f $RPC_URL_ORDERLYOP --broadcast --verifier-url https://testnet-explorer.orderly.org/api\? --verifier blockscout --verify +# orderly mainnet +forge script script/ledgerV2/DeployNewLedger.s.sol -f $RPC_URL_ORDERLYMAIN --broadcast --verifier-url https://explorer.orderly.network/api\? --verifier blockscout --verify +forge script script/ledgerV2/DeployNewOperatorManager.s.sol -f $RPC_URL_ORDERLYMAIN --broadcast --verifier-url https://explorer.orderly.network/api\? --verifier blockscout --verify +forge script script/ledgerV2/DeployNewFeeManager.s.sol -f $RPC_URL_ORDERLYMAIN --broadcast --verifier-url https://explorer.orderly.network/api\? --verifier blockscout --verify +forge script script/ledgerV2/DeployNewVaultManager.s.sol -f $RPC_URL_ORDERLYMAIN --broadcast --verifier-url https://explorer.orderly.network/api\? --verifier blockscout --verify +forge script script/ledgerV2/DeployNewMarketManager.s.sol -f $RPC_URL_ORDERLYMAIN --broadcast --verifier-url https://explorer.orderly.network/api\? --verifier blockscout --verify +forge script script/ledgerV2/DeployNewLedgerImplA.s.sol -f $RPC_URL_ORDERLYMAIN --broadcast --verifier-url https://explorer.orderly.network/api\? --verifier blockscout --verify +``` + +## Vault scripts + +The contracts on Vault side is deployed on EVM-compatiable chains, such as Arbitrum-Goerli, so the rpc is set as RPC_URL_ARBITRUMGOERLI. + +### Deploy command: + +Still the version 2 scripts is used for deployment. The deploy command is as follows: + +```shell +# arb goerli +forge script script/vaultV2/DeployProxyVault.s.sol -f $RPC_URL_ARBITRUMGOERLI --verifier-url https://api-goerli.arbiscan.io/api --broadcast --verify --etherscan-api-key $ARB_ETHERSCAN_API_KEY +# arb sepolia +forge script script/vaultV2/DeployProxyVault.s.sol -f $RPC_URL_ARBITRUMSEPOLIA --verifier-url https://api-sepolia.arbiscan.io/api --broadcast --verify --etherscan-api-key $ARB_ETHERSCAN_API_KEY +# arb mainnet +forge script script/vaultV2/DeployProxyVault.s.sol -f $RPC_URL_ARBITRUM --verifier-url https://api.arbiscan.io/api --broadcast --verify --etherscan-api-key $ARB_ETHERSCAN_API_KEY +# op goerli +forge script script/vaultV2/DeployProxyVault.s.sol -f $RPC_URL_OPGOERLI --verifier-url https://api-goerli-optimistic.etherscan.io/api --broadcast --verify --etherscan-api-key $OP_ETHERSCAN_API_KEY +# op sepolia +forge script script/vaultV2/DeployProxyVault.s.sol -f $RPC_URL_OPSEPOLIA --verifier-url https://api-sepolia-optimistic.etherscan.io/api --broadcast --verify --etherscan-api-key $OP_ETHERSCAN_API_KEY +# op mainnet +forge script script/vaultV2/DeployProxyVault.s.sol -f $RPC_URL_OP --verifier-url https://api-optimistic.etherscan.io/api --broadcast --verify --etherscan-api-key $OP_ETHERSCAN_API_KEY +# polygon mumbai +forge script script/vaultV2/DeployProxyVault.s.sol -f $RPC_URL_MUMBAI --verifier-url https://api-testnet.polygonscan.com/api --broadcast --verify --etherscan-api-key $POLYGON_ETHERSCAN_API_KEY +# polygon mainnet +forge script script/vaultV2/DeployProxyVault.s.sol -f $RPC_URL_POLYGON --verifier-url https://api.polygonscan.com/api --broadcast --verify --etherscan-api-key $POLYGON_ETHERSCAN_API_KEY +``` + +### Deploy new implement command: + +```shell +# arb goerli +forge script script/vaultV2/DeployNewVault.s.sol -f $RPC_URL_ARBITRUMGOERLI --verifier-url https://api-goerli.arbiscan.io/api --broadcast --verify --etherscan-api-key $ARB_ETHERSCAN_API_KEY +# arb sepolia +forge script script/vaultV2/DeployNewVault.s.sol -f $RPC_URL_ARBITRUMSEPOLIA --verifier-url https://api-sepolia.arbiscan.io/api --broadcast --verify --etherscan-api-key $ARB_ETHERSCAN_API_KEY +# polygon mumbai +forge script script/vaultV2/DeployNewVault.s.sol -f $RPC_URL_MUMBAI --verifier-url https://api-testnet.polygonscan.com/api --broadcast --verify --etherscan-api-key $POLYGON_ETHERSCAN_API_KEY + +# arb mainnet +forge script script/vaultV2/DeployNewVault.s.sol -f $RPC_URL_ARBITRUM --verifier-url https://api.arbiscan.io/api --broadcast --verify --etherscan-api-key $ARB_ETHERSCAN_API_KEY +# op goerli +forge script script/vaultV2/DeployNewVault.s.sol -f $RPC_URL_OPGOERLI --verifier-url https://api-goerli-optimistic.etherscan.io/api --broadcast --verify --etherscan-api-key $OP_ETHERSCAN_API_KEY +# op sepolia +forge script script/vaultV2/DeployNewVault.s.sol -f $RPC_URL_OPSEPOLIA --verifier-url https://api-sepolia-optimistic.etherscan.io/api --broadcast --verify --etherscan-api-key $OP_ETHERSCAN_API_KEY +# op mainnet +forge script script/vaultV2/DeployNewVault.s.sol -f $RPC_URL_OP --verifier-url https://api-optimistic.etherscan.io/api --broadcast --verify --etherscan-api-key $OP_ETHERSCAN_API_KEY +``` + +### Set Cross-Chain Manager + +Once the contracts on Vault side are deployed, the Cross-Chain Manager should be set for Vault contract. The command to set Cross-Chain Manager is as follows: + +```shell +# arb goerli +forge script script/ledgerV2/SetCrossChainManager.s.sol -f $RPC_URL_ARBITRUMGOERLI --broadcast +# arb mainnet +forge script script/ledgerV2/SetCrossChainManager.s.sol -f $RPC_URL_ARBITRUM --broadcast +# op goerli +forge script script/ledgerV2/SetCrossChainManager.s.sol -f $RPC_URL_OPGOERLI --broadcast +# op mainnet +forge script script/ledgerV2/SetCrossChainManager.s.sol -f $RPC_URL_OP --broadcast +``` + +### Upgrade command: + +The upgrade model is the same as Ledger side, the upgrade command is as follows: + +```shell +# arb goerli +forge script script/vaultV2/UpgradeVault.s.sol -f $RPC_URL_ARBITRUMGOERLI --broadcast --verifier-url https://api-goerli.arbiscan.io/api --broadcast --verify --etherscan-api-key $ARB_ETHERSCAN_API_KEY +# arb mainnet +forge script script/vaultV2/UpgradeVault.s.sol -f $RPC_URL_ARBITRUM --broadcast --verifier-url https://api.arbiscan.io/api --broadcast --verify --etherscan-api-key $ARB_ETHERSCAN_API_KEY +# op goerli +forge script script/vaultV2/UpgradeVault.s.sol -f $RPC_URL_OPGOERLI --broadcast --verifier-url https://api-goerli-optimistic.etherscan.io/api --broadcast --verify --etherscan-api-key $OP_ETHERSCAN_API_KEY +# op mainnet +forge script script/vaultV2/UpgradeVault.s.sol -f $RPC_URL_OP --broadcast --verifier-url https://api-optimistic.etherscan.io/api --broadcast --verify --etherscan-api-key $OP_ETHERSCAN_API_KEY +``` + +## Zip scripts + +### Deploy command: + +The Zip contract on Ledger side is deployed on Orderly L2 based OP Stack, so the rpc is set as RPC_URL_ORDERLYOP. The deploy command is as follows: + +```shell +# orderly testnet +forge script script/zip/DeployProxyZip.s.sol -f $RPC_URL_ORDERLYOP --broadcast --verifier-url https://testnet-explorer.orderly.org/api\? --verifier blockscout --verify +# orderly mainnet +forge script script/zip/DeployProxyZip.s.sol -f $RPC_URL_ORDERLYMAIN --broadcast --verifier-url https://explorer.orderly.network/api\? --verifier blockscout --verify +``` + +After executing the command, the deployed contracts are Zip proxy contract, Zip implementation contract and the ProxyAdmin contract. + +The addresses of the deployed contracts will be listed inside `config` folder, named as `deploy-zip.json` file. + +### Set OpearatorManager and Operator Address + +Once the contracts are deployed, the Operator EOA address and OperatorManager contract should be set for Zip contract. The command to set is as follows: + +```shell +# orderly testnet +forge script script/zip/SetOperatorAddress.s.sol -f $RPC_URL_ORDERLYOP --broadcast +# orderly mainnet +forge script script/zip/SetOperatorAddress.s.sol -f $RPC_URL_ORDERLYMAIN --broadcast +``` + +### Deploy new implement command: + +```shell +# orderly testnet +forge script script/zip/DeployNewZip.s.sol -f $RPC_URL_ORDERLYOP --broadcast --verifier-url https://testnet-explorer.orderly.org/api\? --verifier blockscout --verify +# orderly mainnet +forge script script/zip/DeployNewZip.s.sol -f $RPC_URL_ORDERLYMAIN --broadcast --verifier-url https://explorer.orderly.network/api\? --verifier blockscout --verify +``` + +### Upgrade command: + +Transparent upgrade pattern is used for Zip contract, to upgrade a specific contract, the corresponding upgrade script should be executed. The upgrade command is as follows: + +```shell +# orderly testnet +forge script script/zip/UpgradeZip.s.sol -f $RPC_URL_ORDERLYOP --broadcast --verifier-url https://testnet-explorer.orderly.org/api\? --verifier blockscout --verify + +# orderly mainnet +forge script script/zip/UpgradeZip.s.sol -f $RPC_URL_ORDERLYMAIN --broadcast --verifier-url https://testnet-explorer.orderly.org/api\? --verifier blockscout --verify +``` diff --git a/compiler_config.json b/compiler_config.json new file mode 100644 index 0000000..063b899 --- /dev/null +++ b/compiler_config.json @@ -0,0 +1,35 @@ +{ + "language": "Solidity", + "settings": { + "optimizer": { + "enabled": true, + "runs": 200 + }, + "outputSelection": { + "*": { + "": [ + "ast" + ], + "*": [ + "abi", + "metadata", + "devdoc", + "userdoc", + "storageLayout", + "evm.legacyAssembly", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", + "evm.gasEstimates", + "evm.assembly" + ] + } + }, + "remappings": [ + "ds-test/=lib/forge-std/lib/ds-test/src/", + "forge-std/=lib/forge-std/src/", + "openzeppelin-contracts/=lib/openzeppelin-contracts/", + "openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/" + ] + } +} \ No newline at end of file diff --git a/config/cross-chain-manager.json b/config/cross-chain-manager.json new file mode 100644 index 0000000..8ae86b5 --- /dev/null +++ b/config/cross-chain-manager.json @@ -0,0 +1,30 @@ +{ + "qa-mock": { + "fuji": { + "manager": "0x25eB1e506608ab7eb8f91802e1dae6963848ED25", + "owner": "0xF6e22738295Af46e8B0dd35F7426f0f83aD0ff6C", + "proxy": "0xb3461f0e51E32bb77cB19D47bf8536E88CC884d2", + "role": "vault" + }, + "orderly": { + "manager": "0x25eB1e506608ab7eb8f91802e1dae6963848ED25", + "owner": "0xF6e22738295Af46e8B0dd35F7426f0f83aD0ff6C", + "proxy": "0x78398524Ab8500736B56eaAb1a590733c2B04144", + "role": "ledger" + } + }, + "qa": { + "orderlyop": { + "manager": "0x25eB1e506608ab7eb8f91802e1dae6963848ED25", + "owner": "0xF6e22738295Af46e8B0dd35F7426f0f83aD0ff6C", + "proxy": "0x2266a110c358468e0Fcb173fE77Ce388889483e8", + "role": "ledger" + }, + "arbitrumgoerli": { + "manager": "0x25eB1e506608ab7eb8f91802e1dae6963848ED25", + "owner": "0xF6e22738295Af46e8B0dd35F7426f0f83aD0ff6C", + "proxy": "0x2266a110c358468e0Fcb173fE77Ce388889483e8", + "role": "vault" + } + } +} \ No newline at end of file diff --git a/config/deploy-ledger.json b/config/deploy-ledger.json new file mode 100644 index 0000000..239ef3e --- /dev/null +++ b/config/deploy-ledger.json @@ -0,0 +1,50 @@ +{ + "dev": { + "orderlyop": { + "feeManager": "0x835E970110E4a46BCA21A7551FEaA5F532F72701", + "ledger": "0x8794E7260517B1766fc7b55cAfcd56e6bf08600e", + "marketManager": "0x3ac2Ba11Ca2f9f109d50fb1a46d4C23fCadbbef6", + "multiSig": "0xFae9CAF31EeD9f6480262808920dA03eb7f76E7E", + "operatorAddress": "0xDdDd1555A17d3Dad86748B883d2C1ce633A7cd88", + "operatorManager": "0xe34614EB781C5838C78B7f913b89A05e7a5b97e2", + "proxyAdmin": "0x8910A067473C1800b371183124AEdC95684244DE", + "vaultManager": "0x4922872C26Befa37AdcA287fF68106013C82FeeD" + } + }, + "qa": { + "orderlyop": { + "feeManager": "0x8A929891DE9a648B6A3D05d21362418f756cf728", + "ledger": "0x50F59504D3623Ad99302835da367676d1f7E3D44", + "marketManager": "0x1AFE8286eD1b365671870A735f7deb4dcc9DB16D", + "multiSig": "0xc1465019B3e04602a50d34A558c6630Ac50f8fbb", + "operatorAddress": "0x314d042d164BbEF71924f19A3913F65C0aCFb94E", + "operatorManager": "0x7Cd1FBdA284997Be499D3294C9a50352Dd682155", + "proxyAdmin": "0x0EaC556c0C2321BA25b9DC01e4e3c95aD5CDCd2f", + "vaultManager": "0x3B092aEe40Cb99174E8C73eF90935F9F35943B22" + } + }, + "staging": { + "orderlyop": { + "feeManager": "0x0B98ba78DDb29937d895c718ED167DD8f5B2972d", + "ledger": "0x1826B75e2ef249173FC735149AE4B8e9ea10abff", + "marketManager": "0x523Ab490B15803d6Ba60dC95F1579536F95edD4e", + "multiSig": "0x7D1e7BeAd9fBb72e35Dc8E6d1966c2e57DbDA3F0", + "operatorAddress": "0x2d4e9C592b9f42557DAE7B103F3fCA47448DC0BD", + "operatorManager": "0x1A46be28AB241F5A64F82ddFc384911520E3d557", + "proxyAdmin": "0x24ea35cCD9d8c7aA5D35BDdc140A5f97D89289F8", + "vaultManager": "0x873c120b42C80D528389d85cEA9d4dC0197974aD" + } + }, + "mainnet": { + "orderlymain": { + "feeManager": "0x343Ca787e960cB2cCA0ce8cfB2f38c3739E28a1E", + "ledger": "0x6F7a338F2aA472838dEFD3283eB360d4Dff5D203", + "marketManager": "0x9281CBc1e37d3bcDB8BAddFa4302B6eb5DAd2672", + "multiSig": "0x4e834Ca9310d7710a409638A7aa70CB22F141Df3", + "operatorAddress": "0x81698d4bE3C2CDE0Fbd2cc457bA1Aa0d5df11DDD", + "operatorManager": "0x7CC5B6433eb33164c88F6512f56C566CFC3420BF", + "proxyAdmin": "0xA2eA0a58b083c492AdC91A687FAc8B53AdB7c0Fd", + "vaultManager": "0x14a6342A8C1Ef9856898F510FcCE377e46668F33" + } + } +} \ No newline at end of file diff --git a/config/deploy-vault.json b/config/deploy-vault.json new file mode 100644 index 0000000..7dc97b9 --- /dev/null +++ b/config/deploy-vault.json @@ -0,0 +1,118 @@ +{ + "dev": { + "arbitrumgoerli": { + "multiSig": "0xFae9CAF31EeD9f6480262808920dA03eb7f76E7E", + "proxyAdmin": "0x93A5486E16553eb112Ec1Fa41f5B8b9E24102B6e", + "usdc": "0xfd064A18f3BF249cf1f87FC203E90D8f650f2d63", + "vault": "0x0C554dDb6a9010Ed1FD7e50d92559A06655dA482" + }, + "opgoerli": { + "multiSig": "0xFae9CAF31EeD9f6480262808920dA03eb7f76E7E", + "proxyAdmin": "0xBfF90C01458f452225A8da88bfEB30c53a99562f", + "usdc": "0xe05606174bac4A6364B31bd0eCA4bf4dD368f8C6", + "vault": "0xe014a38501cF2B8139dED0b60Ed4FbD02C113b59" + }, + "arbitrumsepolia": { + "multiSig": "0xFae9CAF31EeD9f6480262808920dA03eb7f76E7E", + "proxyAdmin": "0x8794E7260517B1766fc7b55cAfcd56e6bf08600e", + "usdc": "0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d", + "vault": "0x3ac2Ba11Ca2f9f109d50fb1a46d4C23fCadbbef6" + }, + "opsepolia": { + "multiSig": "0xFae9CAF31EeD9f6480262808920dA03eb7f76E7E", + "proxyAdmin": "0xEcb4abe96113c9caA3204e96C63C5377D02cb636", + "usdc": "0x5fd84259d66Cd46123540766Be93DFE6D43130D7", + "vault": "0x08a17582C4cCe303C37439341c6E6138Fb62304d" + }, + "polygonmumbai": { + "multiSig": "0xFae9CAF31EeD9f6480262808920dA03eb7f76E7E", + "proxyAdmin": "0xEFCa93d6e7AD45c62B47aA01cafC2D0731B52F3e", + "usdc": "0x9999f7fea5938fd3b1e26a12c3f2fb024e194f97", + "vault": "0x56604D4097042f7950Eb8BFc58b0Dff072089070" + } + }, + "qa": { + "arbitrumgoerli": { + "multiSig": "0xc1465019B3e04602a50d34A558c6630Ac50f8fbb", + "proxyAdmin": "0xCaE33d6D72cd2c01b71d6Be3CE2E62b4B7297961", + "usdc": "0xfd064A18f3BF249cf1f87FC203E90D8f650f2d63", + "vault": "0x22b10472d3Da206aaA85D3f19E91A8da15E0F56A" + }, + "opgoerli": { + "multiSig": "0xc1465019B3e04602a50d34A558c6630Ac50f8fbb", + "proxyAdmin": "0xAbCA777A0439Fc13ff7bA472d85DBb82D83E7738", + "usdc": "0xe05606174bac4A6364B31bd0eCA4bf4dD368f8C6", + "vault": "0x7fc077e4487bb03D393a0C2b87B52798E19602DB" + }, + "arbitrumsepolia": { + "multiSig": "0xc1465019B3e04602a50d34A558c6630Ac50f8fbb", + "proxyAdmin": "0xA603f6e124259d37e43dd5008cB7613164D6a6e3", + "usdc": "0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d", + "vault": "0xB15a3a4D451311e03e34d9331C695078Ad5Cf5F1" + }, + "opsepolia": { + "multiSig": "0xc1465019B3e04602a50d34A558c6630Ac50f8fbb", + "proxyAdmin": "0x0FF2537214614d82335B1621a0bEBECA41045662", + "usdc": "0x5fd84259d66Cd46123540766Be93DFE6D43130D7", + "vault": "0xd5164A5a83c64E59F842bC091E06614b84D95fF5" + }, + "polygonmumbai": { + "multiSig": "0xc1465019B3e04602a50d34A558c6630Ac50f8fbb", + "proxyAdmin": "0x013a4fFc20d31a32BFDa5d6Ea79e5858aC34822f", + "usdc": "0x9999f7fea5938fd3b1e26a12c3f2fb024e194f97", + "vault": "0x7C4570Db29bF8dF8Aa0dBF6bdE637264F9E0Bd67" + } + }, + "staging": { + "arbitrumgoerli": { + "multiSig": "0x7D1e7BeAd9fBb72e35Dc8E6d1966c2e57DbDA3F0", + "proxyAdmin": "0x3ec2259a0e208867CD812b3b1e7A980Bab5FB38D", + "usdc": "0xfd064A18f3BF249cf1f87FC203E90D8f650f2d63", + "vault": "0xd64AeB281f3E8cd70e668b6cb24De7e532dC214D" + }, + "opgoerli": { + "multiSig": "0x7D1e7BeAd9fBb72e35Dc8E6d1966c2e57DbDA3F0", + "proxyAdmin": "0xa2Bcd564FdA37d10193caF6644cf0990A226ACef", + "usdc": "0xe05606174bac4A6364B31bd0eCA4bf4dD368f8C6", + "vault": "0x1bae7b601760A45710B43Da2C8f8a3c933A144D2" + }, + "arbitrumsepolia": { + "multiSig": "0x7D1e7BeAd9fBb72e35Dc8E6d1966c2e57DbDA3F0", + "proxyAdmin": "0xa3BB1F1Aa238AF13528965f069fD0a4F1595d8e9", + "usdc": "0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d", + "vault": "0x0EaC556c0C2321BA25b9DC01e4e3c95aD5CDCd2f" + }, + "opsepolia": { + "multiSig": "0x7D1e7BeAd9fBb72e35Dc8E6d1966c2e57DbDA3F0", + "proxyAdmin": "0xEfD0cbED3f520cb59c6Cf8cE428a44978C18F8Fb", + "usdc": "0x5fd84259d66Cd46123540766Be93DFE6D43130D7", + "vault": "0xEfF2896077B6ff95379EfA89Ff903598190805EC" + }, + "polygonmumbai": { + "multiSig": "0x7D1e7BeAd9fBb72e35Dc8E6d1966c2e57DbDA3F0", + "proxyAdmin": "0xAf0736bDE60E4Df8a0017839B35bed4895a52e93", + "usdc": "0x9999f7fea5938fd3b1e26a12c3f2fb024e194f97", + "vault": "0xB483f9D0bD47371B54087983214887c3Be103b92" + } + }, + "mainnet": { + "arbitrum": { + "multiSig": "0x4e834Ca9310d7710a409638A7aa70CB22F141Df3", + "proxyAdmin": "0xA2eA0a58b083c492AdC91A687FAc8B53AdB7c0Fd", + "usdc": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", + "vault": "0x816f722424B49Cf1275cc86DA9840Fbd5a6167e9" + }, + "op": { + "multiSig": "0x4e834Ca9310d7710a409638A7aa70CB22F141Df3", + "proxyAdmin": "0xA2eA0a58b083c492AdC91A687FAc8B53AdB7c0Fd", + "usdc": "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85", + "vault": "0x816f722424B49Cf1275cc86DA9840Fbd5a6167e9" + }, + "polygon": { + "multiSig": "0x4e834Ca9310d7710a409638A7aa70CB22F141Df3", + "proxyAdmin": "0xA2eA0a58b083c492AdC91A687FAc8B53AdB7c0Fd", + "usdc": "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359", + "vault": "0x816f722424B49Cf1275cc86DA9840Fbd5a6167e9" + } + } +} \ No newline at end of file diff --git a/config/deploy-zip.json b/config/deploy-zip.json new file mode 100644 index 0000000..463e199 --- /dev/null +++ b/config/deploy-zip.json @@ -0,0 +1,22 @@ +{ + "dev": { + "orderlyop": { + "zip": "0xDdE9B764E58EC26086621919F4388359715F0f84" + } + }, + "qa": { + "orderlyop": { + "zip": "0x33076080b36a9827ead8016D102071df9eC2C89f" + } + }, + "staging": { + "orderlyop": { + "zip": "0x3F13660d45080b7BD9c899298Ebe155904124EBa" + } + }, + "mainnet": { + "orderlymain": { + "zip": "0xCC083CF647D6bfBdc4260d2bF2962737FC979438" + } + } +} \ No newline at end of file diff --git a/config/project-related.json b/config/project-related.json new file mode 100644 index 0000000..6c4a7a9 --- /dev/null +++ b/config/project-related.json @@ -0,0 +1,11 @@ +{ + "qa": { + "arbitrumgoerli": { + "vault": "0x22b10472d3Da206aaA85D3f19E91A8da15E0F56A" + }, + "orderlyop": { + "ledger": "0x50F59504D3623Ad99302835da367676d1f7E3D44", + "operator-manager": "0x7Cd1FBdA284997Be499D3294C9a50352Dd682155" + } + } +} \ No newline at end of file diff --git a/config/tasks/ledger-vault-envs.json b/config/tasks/ledger-vault-envs.json new file mode 100644 index 0000000..58b335e --- /dev/null +++ b/config/tasks/ledger-vault-envs.json @@ -0,0 +1,5 @@ +{ + "env": "dev", + "ledgerNetwork": "orderlyop", + "vaultNetwork": "polygonmumbai" +} \ No newline at end of file diff --git a/config/tasks/qa-ccManagerDeploy.json b/config/tasks/qa-ccManagerDeploy.json new file mode 100644 index 0000000..1da886d --- /dev/null +++ b/config/tasks/qa-ccManagerDeploy.json @@ -0,0 +1,5 @@ +{ + "env": "qa", + "vaultNetwork": "arbitrumgoerli", + "ledgerNetwork": "orderlyop" +} \ No newline at end of file diff --git a/config/tasks/qa-sendCCTest.json b/config/tasks/qa-sendCCTest.json new file mode 100644 index 0000000..1da886d --- /dev/null +++ b/config/tasks/qa-sendCCTest.json @@ -0,0 +1,5 @@ +{ + "env": "qa", + "vaultNetwork": "arbitrumgoerli", + "ledgerNetwork": "orderlyop" +} \ No newline at end of file diff --git a/config/tasks/qa-setupCCManager.json b/config/tasks/qa-setupCCManager.json new file mode 100644 index 0000000..1da886d --- /dev/null +++ b/config/tasks/qa-setupCCManager.json @@ -0,0 +1,5 @@ +{ + "env": "qa", + "vaultNetwork": "arbitrumgoerli", + "ledgerNetwork": "orderlyop" +} \ No newline at end of file diff --git a/config/tasks/qa-upgradeCCManager.json b/config/tasks/qa-upgradeCCManager.json new file mode 100644 index 0000000..8e47877 --- /dev/null +++ b/config/tasks/qa-upgradeCCManager.json @@ -0,0 +1,7 @@ +{ + "env": "qa", + "vaultNetwork": "arbitrumgoerli", + "ledgerNetwork": "orderlyop", + "upradeVault": 0, + "upgradeLedger": 0 +} \ No newline at end of file diff --git a/easyScripts/deployCCManager.s.sol b/easyScripts/deployCCManager.s.sol new file mode 100644 index 0000000..eace0a2 --- /dev/null +++ b/easyScripts/deployCCManager.s.sol @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.10; + +import "cc-relay/baseScripts/BaseScript.s.sol"; +import "cc-relay/baseScripts/ConfigHelper.s.sol"; +import "../src/LedgerCrossChainManagerUpgradeable.sol"; +import "../src/CrossChainManagerProxy.sol"; +import "../src/VaultCrossChainManagerUpgradeable.sol"; + +contract DeployCCManager is BaseScript, ConfigHelper { + using StringUtils for string; + + // variable order must be alphabetical + struct DeployCCManagerConfig { + string env; + string ledgerNetwork; + string vaultNetwork; + } + + function run() external { + bytes memory encodedData = getConfigFileData("DEPLOY_CCMANAGER_CONFIG_FILE"); + DeployCCManagerConfig memory config = abi.decode(encodedData, (DeployCCManagerConfig)); + console.log("vaultNetwork: ", config.vaultNetwork); + console.log("ledgerNetwork: ", config.ledgerNetwork); + console.log("env: ", config.env); + deployLedger(config.ledgerNetwork, config.env); + deployVault(config.vaultNetwork, config.env); + } + + function deployLedger(string memory network, string memory env) internal { + console.log("network: ", network); + + uint256 pk = getPrivateKey(network); + + vmSelectRpcAndBroadcast(network); + + LedgerCrossChainManagerUpgradeable ledger = new LedgerCrossChainManagerUpgradeable(); + console.log("deployed ledger address: ", address(ledger)); + CrossChainManagerProxy ledgerProxy = new CrossChainManagerProxy(address(ledger), bytes("")); + console.log("deployed ledger proxy address: ", address(ledgerProxy)); + LedgerCrossChainManagerUpgradeable ledgerUpgradeable = + LedgerCrossChainManagerUpgradeable(payable(address(ledgerProxy))); + ledgerUpgradeable.initialize(); + + string memory deploySaveFile = vm.envString("DEPLOY_CCMANAGER_SAVE_FILE"); + writeToJsonFileByKey(vm.toString(address(ledgerProxy)), deploySaveFile, env, network, "proxy"); + writeToJsonFileByKey(vm.toString(address(ledger)), deploySaveFile, env, network, "manager"); + writeToJsonFileByKey(vm.toString(vm.addr(pk)), deploySaveFile, env, network, "owner"); + writeToJsonFileByKey("ledger", deploySaveFile, env, network, "role"); + + vm.stopBroadcast(); + } + + function deployVault(string memory network, string memory env) internal { + console.log("network: ", network); + + uint256 pk = getPrivateKey(network); + + vmSelectRpcAndBroadcast(network); + + VaultCrossChainManagerUpgradeable vault = new VaultCrossChainManagerUpgradeable(); + console.log("deployed vault address: ", address(vault)); + CrossChainManagerProxy vaultProxy = new CrossChainManagerProxy(address(vault), bytes("")); + console.log("deployed vault proxy address: ", address(vaultProxy)); + VaultCrossChainManagerUpgradeable vaultUpgradeable = + VaultCrossChainManagerUpgradeable(payable(address(vaultProxy))); + vaultUpgradeable.initialize(); + + string memory deploySaveFile = vm.envString("DEPLOY_CCMANAGER_SAVE_FILE"); + writeToJsonFileByKey(vm.toString(address(vaultProxy)), deploySaveFile, env, network, "proxy"); + writeToJsonFileByKey(vm.toString(address(vault)), deploySaveFile, env, network, "manager"); + writeToJsonFileByKey(vm.toString(vm.addr(pk)), deploySaveFile, env, network, "owner"); + writeToJsonFileByKey("vault", deploySaveFile, env, network, "role"); + + vm.stopBroadcast(); + } +} diff --git a/easyScripts/easy_script.sh b/easyScripts/easy_script.sh new file mode 100644 index 0000000..a1d9a22 --- /dev/null +++ b/easyScripts/easy_script.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +# Check that exactly two arguments were provided +if [ $# -ne 2 ]; then + echo "Usage: $0 method_name value" + exit 1 +fi + +method_name="$1" +value="$2" + +echo "method_name: $method_name" +echo "value: $value" + +# Path to the .env file +env_file=".env" + +# Define a mapping from method names to variable names +# Add more mappings as needed +if [ "$method_name" == "deploy" ]; then + var_name="DEPLOY_CCMANAGER_CONFIG_FILE" +elif [ "$method_name" == "setup" ]; then + var_name="SETUP_CCMANAGER_CONFIG_FILE" +else + echo "Invalid method name: $method_name" + exit 2 +fi + +# print the variable name +echo $var_name + +# Check if the variable exists in the .env file +if grep -q "^$var_name=" "$env_file"; then + # If it does, use sed to replace the line with the new value + # show command before executing + echo "sed -i \"\" \"s/^$var_name=.*/$var_name=\"$value\"/\" \"$env_file\"" + sed -i "" "s|^$var_name=.*|$var_name=\"$value\"|" "$env_file" + echo "$var_name has been updated to $value in $env_file file." +else + # If it doesn't, append the new variable and value to the .env file + echo "$var_name=$value" >> "$env_file" + echo "$var_name has been added with value $value to $env_file file." +fi \ No newline at end of file diff --git a/easyScripts/sendCCTest.s.sol b/easyScripts/sendCCTest.s.sol new file mode 100644 index 0000000..f73e102 --- /dev/null +++ b/easyScripts/sendCCTest.s.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.10; + +import "cc-relay/baseScripts/BaseScript.s.sol"; +import "cc-relay/baseScripts/ConfigHelper.s.sol"; +import "../src/LedgerCrossChainManagerUpgradeable.sol"; +import "../src/CrossChainManagerProxy.sol"; +import "../src/VaultCrossChainManagerUpgradeable.sol"; + +contract SendCCTest is BaseScript, ConfigHelper { + using StringUtils for string; + + // variable order must be alphabetical + struct SendCCTestConfig { + string env; + string ledgerNetwork; + string vaultNetwork; + } + + function run() external { + bytes memory encodedData = getConfigFileData("SEND_CC_TEST_CONFIG_FILE"); + SendCCTestConfig memory config = abi.decode(encodedData, (SendCCTestConfig)); + console.log("vaultNetwork: ", config.vaultNetwork); + console.log("ledgerNetwork: ", config.ledgerNetwork); + console.log("env: ", config.env); + + CCManagerDeployData memory ledgerDeployData = getCCManagerDeployData(config.env, config.ledgerNetwork); + + LedgerCrossChainManagerUpgradeable ledger = LedgerCrossChainManagerUpgradeable(payable(ledgerDeployData.proxy)); + + uint256 dstChainId = getChainId(config.vaultNetwork); + + vmSelectRpcAndBroadcast(config.ledgerNetwork); + + ledger.sendTestWithdraw(dstChainId); + + vm.stopBroadcast(); + } +} diff --git a/easyScripts/setupCCManager.s.sol b/easyScripts/setupCCManager.s.sol new file mode 100644 index 0000000..0f7a561 --- /dev/null +++ b/easyScripts/setupCCManager.s.sol @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.10; + +import "cc-relay/baseScripts/BaseScript.s.sol"; +import "cc-relay/baseScripts/ConfigHelper.s.sol"; +import "../src/LedgerCrossChainManagerUpgradeable.sol"; +import "../src/CrossChainManagerProxy.sol"; +import "../src/VaultCrossChainManagerUpgradeable.sol"; + +contract SetupCCManager is BaseScript, ConfigHelper { + using StringUtils for string; + + // variable order must be alphabetical + struct SetupCCManagerConfig { + string env; + string ledgerNetwork; + string vaultNetwork; + } + + function run() external { + bytes memory encodedData = getConfigFileData("DEPLOY_CCMANAGER_CONFIG_FILE"); + SetupCCManagerConfig memory config = abi.decode(encodedData, (SetupCCManagerConfig)); + console.log("vaultNetwork: ", config.vaultNetwork); + console.log("ledgerNetwork: ", config.ledgerNetwork); + console.log("env: ", config.env); + + CCManagerDeployData memory vaultDeployData = getCCManagerDeployData(config.env, config.vaultNetwork); + CCManagerDeployData memory ledgerDeployData = getCCManagerDeployData(config.env, config.ledgerNetwork); + + RelayDeployData memory vaultRelayData = getRelayDeployData(config.env, config.vaultNetwork); + RelayDeployData memory ledgerRelayData = getRelayDeployData(config.env, config.ledgerNetwork); + + // logging everything in function call + console.log("vaultDeployData.proxy: ", vaultDeployData.proxy); + console.log("ledgerDeployData.proxy: ", ledgerDeployData.proxy); + console.log("vaultRelayData.proxy: ", vaultRelayData.proxy); + console.log("ledgerRelayData.proxy: ", ledgerRelayData.proxy); + + setupLedger(config.ledgerNetwork, config.env, vaultDeployData.proxy, vaultRelayData.proxy); + + setupVault( + config.vaultNetwork, + config.ledgerNetwork, + config.env, + vaultDeployData.proxy, + ledgerDeployData.proxy, + vaultRelayData.proxy + ); + } + + function setupLedger(string memory network, string memory env, address managerAddress, address relayAddress) + internal + { + console.log("network: ", network); + + uint256 chainId = getChainId(network); + + string memory projectRelatedFile = vm.envString("DEPLOY_PROJECT_RELATED_FILE"); + + bytes memory ledgerEncodedData = getValueByKey(projectRelatedFile, env, network, "ledger"); + bytes memory operatorEncodedData = getValueByKey(projectRelatedFile, env, network, "operator-manager"); + address ledgerAddr = abi.decode(ledgerEncodedData, (address)); + address operatorAddr = abi.decode(operatorEncodedData, (address)); + + vmSelectRpcAndBroadcast(network); + + LedgerCrossChainManagerUpgradeable ledger = LedgerCrossChainManagerUpgradeable(payable(managerAddress)); + + ledger.setChainId(chainId); + ledger.setLedger(ledgerAddr); + ledger.setOperatorManager(operatorAddr); + ledger.setCrossChainRelay(relayAddress); + + ledger.setTokenDecimal(0xd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa, 4460, 6); + ledger.setTokenDecimal(0xd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa, 421613, 6); + + vm.stopBroadcast(); + } + + function setupVault( + string memory network, + string memory ledgerNetwork, + string memory env, + address managerAddress, + address ledgerManagerAddress, + address relayAddress + ) internal { + console.log("network: ", network); + + uint256 chainId = getChainId(network); + uint256 ledgerChainId = getChainId(ledgerNetwork); + + string memory projectRelatedFile = vm.envString("DEPLOY_PROJECT_RELATED_FILE"); + + bytes memory vaultEncodedData = getValueByKey(projectRelatedFile, env, network, "vault"); + address vaultAddr = abi.decode(vaultEncodedData, (address)); + + vmSelectRpcAndBroadcast(network); + + VaultCrossChainManagerUpgradeable vault = VaultCrossChainManagerUpgradeable(payable(managerAddress)); + + vault.setChainId(chainId); + vault.setVault(vaultAddr); + vault.setCrossChainRelay(relayAddress); + vault.setLedgerCrossChainManager(ledgerChainId, ledgerManagerAddress); + + vm.stopBroadcast(); + } +} diff --git a/easyScripts/upgradeCCManager.s.sol b/easyScripts/upgradeCCManager.s.sol new file mode 100644 index 0000000..c2778b9 --- /dev/null +++ b/easyScripts/upgradeCCManager.s.sol @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.10; + +import "cc-relay/baseScripts/BaseScript.s.sol"; +import "cc-relay/baseScripts/ConfigHelper.s.sol"; +import "../src/LedgerCrossChainManagerUpgradeable.sol"; +import "../src/CrossChainManagerProxy.sol"; +import "../src/VaultCrossChainManagerUpgradeable.sol"; + +contract DeployCCManager is BaseScript, ConfigHelper { + using StringUtils for string; + + // variable order must be alphabetical + struct UpgradeCCManagerConfig { + string env; + string ledgerNetwork; + uint256 upgradeLedger; + uint256 upgradeVault; + string vaultNetwork; + } + + function run() external { + bytes memory encodedData = getConfigFileData("UPGRADE_CCMANAGER_CONFIG_FILE"); + UpgradeCCManagerConfig memory config = abi.decode(encodedData, (UpgradeCCManagerConfig)); + + // logging everything in function call + // check before you wreck + console.log("vaultNetwork: ", config.vaultNetwork); + console.log("ledgerNetwork: ", config.ledgerNetwork); + console.log("upgradeLedger: ", config.upgradeLedger); + console.log("upgradeVault: ", config.upgradeVault); + console.log("env: ", config.env); + + CCManagerDeployData memory ledgerDeployData = getCCManagerDeployData(config.env, config.ledgerNetwork); + + CCManagerDeployData memory vaultDeployData = getCCManagerDeployData(config.env, config.vaultNetwork); + if (config.upgradeLedger == 1) { + upgradeLedger(config.ledgerNetwork, ledgerDeployData.proxy, ledgerDeployData.owner, config.env); + } + if (config.upgradeVault == 1) { + upgradeVault(config.vaultNetwork, vaultDeployData.proxy, vaultDeployData.owner, config.env); + } + } + + function upgradeLedger(string memory network, address managerProxy, address owner, string memory env) internal { + console.log("network: ", network); + + uint256 pk = getPrivateKey(network); + require(owner == vm.addr(pk), "owner must be the same as the previous one"); + + vmSelectRpcAndBroadcast(network); + + LedgerCrossChainManagerUpgradeable ledger = new LedgerCrossChainManagerUpgradeable(); + console.log("deployed new ledger cc manager address: ", address(ledger)); + LedgerCrossChainManagerUpgradeable ledgerProxy = LedgerCrossChainManagerUpgradeable(payable(managerProxy)); + console.log("ledger proxy address: ", address(ledgerProxy)); + + ledgerProxy.upgradeTo(address(ledger)); + + writeCCManagerDeployData(env, network, "manager", vm.toString(address(ledger))); + + vm.stopBroadcast(); + } + + function upgradeVault(string memory network, address managerProxy, address owner, string memory env) internal { + console.log("network: ", network); + + uint256 pk = getPrivateKey(network); + require(owner == vm.addr(pk), "owner must be the same as the previous one"); + + vmSelectRpcAndBroadcast(network); + + VaultCrossChainManagerUpgradeable vault = new VaultCrossChainManagerUpgradeable(); + console.log("deployed new vault cc manager address: ", address(vault)); + VaultCrossChainManagerUpgradeable vaultProxy = VaultCrossChainManagerUpgradeable(payable(managerProxy)); + console.log("vault proxy address: ", address(vaultProxy)); + + vaultProxy.upgradeTo(address(vault)); + + writeCCManagerDeployData(env, network, "manager", vm.toString(address(vault))); + + vm.stopBroadcast(); + } +} diff --git a/foundry.toml b/foundry.toml index d7be1a4..bcdfcc8 100644 --- a/foundry.toml +++ b/foundry.toml @@ -1,9 +1,12 @@ [profile.default] +solc_version = "0.8.19" src = 'src' out = 'out' libs = ['lib'] -optimizer_runs = 1000000 +optimizer_runs = 1000 verbosity = 1 +fs_permissions = [{ access = "read-write", path = "./" }] + [profile.ci] fuzz_runs = 100_000 diff --git a/lib/create3-factory b/lib/create3-factory deleted file mode 160000 index 06ec0ff..0000000 --- a/lib/create3-factory +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 06ec0ff36d41853dcd4399fbe2127aef801c4077 diff --git a/lib/openzeppelin-contracts-upgradeable b/lib/openzeppelin-contracts-upgradeable new file mode 160000 index 0000000..bc95521 --- /dev/null +++ b/lib/openzeppelin-contracts-upgradeable @@ -0,0 +1 @@ +Subproject commit bc95521e34dcd49792065e264a7ad2b5a86f0091 diff --git a/lib/solmate b/lib/solmate deleted file mode 160000 index 2001af4..0000000 --- a/lib/solmate +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 2001af43aedb46fdc2335d2a7714fb2dae7cfcd1 diff --git a/myScript/BaseScript.s.sol b/myScript/BaseScript.s.sol new file mode 100644 index 0000000..23dbcc0 --- /dev/null +++ b/myScript/BaseScript.s.sol @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +import "forge-std/Script.sol"; +import "./Utils.sol"; + +contract BaseScript is Script { + using StringCompare for string; + + function getPrivateKey(string memory network) internal view returns (uint256) { + if (network.compare("fuji")) { + return vm.envUint("FUJI_PRIVATE_KEY"); + } else if (network.compare("mumbai")) { + return vm.envUint("MUMBAI_PRIVATE_KEY"); + } else if (network.compare("orderly")) { + return vm.envUint("ORDERLY_PRIVATE_KEY"); + } else if (network.compare("orderlyop")) { + return vm.envUint("ORDERLYOP_PRIVATE_KEY"); + } else if (network.compare("arbitrumgoerli")) { + return vm.envUint("ARBITRUMGOERLI_PRIVATE_KEY"); + } else { + revert("Invalid network"); + } + } + + function getLzEndpoint(string memory network) internal view returns (address) { + if (network.compare("fuji")) { + return vm.envAddress("FUJI_ENDPOINT"); + } else if (network.compare("mumbai")) { + return vm.envAddress("MUMBAI_ENDPOINT"); + } else if (network.compare("orderly")) { + return vm.envAddress("ORDERLY_ENDPOINT"); + } else if (network.compare("orderlyop")) { + return vm.envAddress("ORDERLYOP_ENDPOINT"); + } else if (network.compare("arbitrumgoerli")) { + return vm.envAddress("ARBITRUMGOERLI_ENDPOINT"); + } else { + revert("Invalid network"); + } + } + + function getRelayProxyAddress(string memory network) internal view returns (address) { + if (network.compare("fuji")) { + return vm.envAddress("FUJI_RELAY_PROXY"); + } else if (network.compare("mumbai")) { + return vm.envAddress("MUMBAI_RELAY_PROXY"); + } else if (network.compare("orderly")) { + return vm.envAddress("ORDERLY_RELAY_PROXY"); + } else if (network.compare("orderlyop")) { + return vm.envAddress("ORDERLYOP_RELAY_PROXY"); + } else if (network.compare("arbitrumgoerli")) { + return vm.envAddress("ARBITRUMGOERLI_RELAY_PROXY"); + } else { + revert("Invalid network"); + } + } + + function getManagerProxyAddress(string memory network) internal view returns (address) { + if (network.compare("fuji")) { + return vm.envAddress("FUJI_MANAGER_PROXY"); + } else if (network.compare("mumbai")) { + return vm.envAddress("MUMBAI_MANAGER_PROXY"); + } else if (network.compare("orderly")) { + return vm.envAddress("ORDERLY_MANAGER_PROXY"); + } else if (network.compare("orderlyop")) { + return vm.envAddress("ORDERLYOP_MANAGER_PROXY"); + } else if (network.compare("arbitrumgoerli")) { + return vm.envAddress("ARBITRUMGOERLI_MANAGER_PROXY"); + } else { + revert("Invalid network"); + } + } + + function getChainId(string memory network) internal view returns (uint256) { + if (network.compare("fuji")) { + return vm.envUint("FUJI_CHAIN_ID"); + } else if (network.compare("mumbai")) { + return vm.envUint("MUMBAI_CHAIN_ID"); + } else if (network.compare("orderly")) { + return vm.envUint("ORDERLY_CHAIN_ID"); + } else if (network.compare("orderlyop")) { + return vm.envUint("ORDERLYOP_CHAIN_ID"); + } else if (network.compare("arbitrumgoerli")) { + return vm.envUint("ARBITRUMGOERLI_CHAIN_ID"); + } else { + revert("Invalid network"); + } + } + + function getOperatorManagerAddress(string memory network) internal view returns (address) { + if (network.compare("fuji")) { + return vm.envAddress("FUJI_OPERATOR_MANAGER"); + } else if (network.compare("mumbai")) { + return vm.envAddress("MUMBAI_OPERATOR_MANAGER"); + } else if (network.compare("orderly")) { + return vm.envAddress("ORDERLY_OPERATOR_MANAGER"); + } else if (network.compare("orderlyop")) { + return vm.envAddress("ORDERLYOP_OPERATOR_MANAGER"); + } else if (network.compare("arbitrumgoerli")) { + return vm.envAddress("ARBITRUMGOERLI_OPERATOR_MANAGER"); + } else { + revert("Invalid network"); + } + } + + function getVaultAddress(string memory network) internal view returns (address) { + if (network.compare("fuji")) { + return vm.envAddress("FUJI_VAULT"); + } else if (network.compare("mumbai")) { + return vm.envAddress("MUMBAI_VAULT"); + } else if (network.compare("orderly")) { + return vm.envAddress("ORDERLY_VAULT"); + } else if (network.compare("orderlyop")) { + return vm.envAddress("ORDERLYOP_VAULT"); + } else if (network.compare("arbitrumgoerli")) { + return vm.envAddress("ARBITRUMGOERLI_VAULT"); + } else { + revert("Invalid network"); + } + } + + function getLedgerAddress(string memory network) internal view returns (address) { + if (network.compare("fuji")) { + return vm.envAddress("FUJI_LEDGER"); + } else if (network.compare("mumbai")) { + return vm.envAddress("MUMBAI_LEDGER"); + } else if (network.compare("orderly")) { + return vm.envAddress("ORDERLY_LEDGER"); + } else if (network.compare("orderlyop")) { + return vm.envAddress("ORDERLYOP_LEDGER"); + } else if (network.compare("arbitrumgoerli")) { + return vm.envAddress("ARBITRUMGOERLI_LEDGER"); + } else { + revert("Invalid network"); + } + } +} diff --git a/myScript/CheckEndpoint.s.sol b/myScript/CheckEndpoint.s.sol new file mode 100644 index 0000000..46cbb03 --- /dev/null +++ b/myScript/CheckEndpoint.s.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "forge-std/Script.sol"; +import "../lib/crosschain/contracts/CrossChainRelay.sol"; +import "../src/VaultCrossChainManager.sol"; +import "../lib/crosschain/contracts/layerzero/interfaces/ILayerZeroEndpoint.sol"; + + +contract CheckEndpoint is Script{ + event HasPayload(bool); + function run() external { + uint256 orderlyPrivateKey = vm.envUint("ORDERLY_PRIVATE_KEY"); + address endpoint = vm.envAddress("ORDERLY_ENDPOINT"); + address vaultRelay = vm.envAddress("VAULT_RELAY_ADDRESS"); + address ledgerRelay = vm.envAddress("LEDGER_RELAY_ADDRESS"); + vm.startBroadcast(orderlyPrivateKey); + ILayerZeroEndpoint endpointInstance = ILayerZeroEndpoint(payable(endpoint)); + + uint16 srcChainId = 10106; + bytes memory lzPath = abi.encodePacked(vaultRelay, ledgerRelay); + + bool has = endpointInstance.hasStoredPayload(srcChainId, lzPath); + emit HasPayload(has); + + vm.stopBroadcast(); + } +} diff --git a/myScript/CheckLedger.s.sol b/myScript/CheckLedger.s.sol new file mode 100644 index 0000000..e54b76e --- /dev/null +++ b/myScript/CheckLedger.s.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "forge-std/Script.sol"; +import "../src/Ledger.sol"; + + +contract CheckLedger is Script{ + event Manager(address); + function run() external { + uint256 orderlyPrivateKey = vm.envUint("ORDERLY_PRIVATE_KEY"); + address ledger = 0xAbCA777A0439Fc13ff7bA472d85DBb82D83E7738; + vm.startBroadcast(orderlyPrivateKey); + + Ledger ledgerInstance = Ledger(payable(ledger)); + + address manager = ledgerInstance.crossChainManagerAddress(); + + emit Manager(manager); + vm.stopBroadcast(); + } +} diff --git a/myScript/CrossChainAll.s.sol b/myScript/CrossChainAll.s.sol new file mode 100644 index 0000000..b608811 --- /dev/null +++ b/myScript/CrossChainAll.s.sol @@ -0,0 +1,324 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "forge-std/Script.sol"; +import "../lib/crosschain/contracts/CrossChainRelay.sol"; +import "../src/VaultCrossChainManager.sol"; +import "../src/LedgerCrossChainManager.sol"; +import "../src/VaultCrossChainManagerUpgradeable.sol"; +import "../src/LedgerCrossChainManagerUpgradeable.sol"; +import "../src/CrossChainManagerProxy.sol"; +import "./Utils.sol"; +import "./CrossChainConfig.sol"; +import "../lib/crosschain/contracts/layerzero/interfaces/ILayerZeroEndpoint.sol"; + +contract CrossChainRelaySetup is Script { + using StringCompare for string; + + function run() external { + string memory method = vm.envString("CROSS_CHAIN_SCRIPT_METHOD"); + string memory crosschainOption = vm.envString("CROSS_CHAIN_OPTION"); + string memory currentSide = vm.envString("CROSS_CHAIN_CURRENT_SIDE"); + CrossChainConfig allConfig = new CrossChainConfig(); + CrossChainConfig.ChainConfig memory config = allConfig.getChainConfig(crosschainOption); + //uint256 vaultPrivateKey = getPrivateKey(config.vaultNetwork); + //uint256 ledgerPrivateKey = getPrivateKey(config.ledgerNetwork); + + ( + address relayAddress, + address otherRelayAddress, + address crossChainManagerAddress, + address otherCrossChainManagerAddress, + string memory network, + uint256 srcChainId, + , + //uint16 srcLzChainId, + uint256 dstChainId, + uint16 dstLzChainId + ) = getCrossChainInfo(currentSide, config); + + /* Call Steps + 1. Init + 1. deploy relay(ledger), relay(vault), ledgerCrossChainManager, vaultCrossChainManager + 2. initRelay + 3. initCrossChainManager + 2. WithdrawRelay + 3. transferRelay + 4. SetGasMapping + 5. Update relay + 1. init relay + 2. update relay on cross chain manager + 3. update relay on other side + 6. update cross chain manager + 1. init cross chain manager + 2. update cross chain manager on relay + 3. update cross chain manager on other side + 7. forceResume + */ + vm.startBroadcast(getPrivateKey(network)); + + // initialize cross chain + if (method.compare("deployUpgradeable")) { + deployUpgradeable(currentSide); + } else if (method.compare("init")) { + initRelay(relayAddress, otherRelayAddress, crossChainManagerAddress, srcChainId, dstLzChainId, allConfig); + initCrossChainManager( + currentSide, + relayAddress, + crossChainManagerAddress, + otherCrossChainManagerAddress, + srcChainId, + dstChainId + ); + } else if (method.compare("withdrawRelay")) { + withdrawRelay(relayAddress, network); + } else if (method.compare("transferRelay")) { + transferRelay(relayAddress); + } else if (method.compare("setGasMapping")) { + setGasMapping(crosschainOption, allConfig, relayAddress); + + // when you deploy a new relay, update relay + } else if (method.compare("updateRelay")) { + initRelay(relayAddress, otherRelayAddress, crossChainManagerAddress, srcChainId, dstLzChainId, allConfig); + updateRelayOnCrossChainManager(currentSide, relayAddress, crossChainManagerAddress); + } else if (method.compare("updateOtherSideRelay")) { + updateOtherSideRelay(relayAddress, otherRelayAddress, dstLzChainId); + + // when deploy a new cross chain manager, update cross chain manager + } else if (method.compare("updateCrossChainManager")) { + initCrossChainManager( + currentSide, + relayAddress, + crossChainManagerAddress, + otherCrossChainManagerAddress, + srcChainId, + dstChainId + ); + updateCrossChainManagerOnRelay(relayAddress, crossChainManagerAddress); + } else if (method.compare("updateOtherSideCrossChainManager")) { + updateOtherSideCrossChainManager( + currentSide, crossChainManagerAddress, otherCrossChainManagerAddress, dstChainId + ); + } else if (method.compare("forceResume")) { + forceResumeReceive(relayAddress, otherRelayAddress, dstLzChainId); + } else if (method.compare("deposit")) {} else if (method.compare("retryLz")) { + retryLzCrossChain(relayAddress, otherRelayAddress, getLzEndpoint(network), dstLzChainId); + } else if (method.compare("setVault")) { + VaultCrossChainManager vaultCrossChainManager = VaultCrossChainManager(payable(crossChainManagerAddress)); + vaultCrossChainManager.setVault(vm.envAddress("VAULT_ADDRESS")); + } else if (method.compare("setLedger")) { + LedgerCrossChainManager ledgerCrossChainManager = LedgerCrossChainManager(payable(crossChainManagerAddress)); + ledgerCrossChainManager.setLedger(vm.envAddress("LEDGER_ADDRESS")); + } else { + revert("Invalid method"); + } + + vm.stopBroadcast(); + } + + function deployUpgradeable(string memory side) internal { + if (side.compare("vault")) { + VaultCrossChainManagerUpgradeable vaultCrossChainManager = new VaultCrossChainManagerUpgradeable(); + CrossChainManagerProxy proxy = new CrossChainManagerProxy(address(vaultCrossChainManager), ""); + VaultCrossChainManagerUpgradeable(payable(address(proxy))).initialize(); + } else { + LedgerCrossChainManagerUpgradeable ledgerCrossChainManager = new LedgerCrossChainManagerUpgradeable(); + CrossChainManagerProxy proxy = new CrossChainManagerProxy(address(ledgerCrossChainManager), ""); + LedgerCrossChainManagerUpgradeable(payable(address(proxy))).initialize(); + } + } + + function withdrawRelay(address relayAddress, string memory network) internal { + address signerAddress = vm.addr(getPrivateKey(network)); + CrossChainRelay relay = CrossChainRelay(payable(relayAddress)); + uint256 balance = relayAddress.balance; + + relay.withdrawNativeToken(payable(signerAddress), balance); + } + + function transferRelay(address relayAddress) internal { + uint256 transferAmount = vm.envUint("RELAY_TRANSFER_AMOUNT"); + + (bool ret,) = payable(relayAddress).call{value: transferAmount}(""); + require(ret, "Transfer relay failed"); + } + + function initRelay( + address relayAddress, + address otherRelayAddress, + address crossChainManagerAddress, + uint256 srcChainId, + uint16 dstLzChainId, + CrossChainConfig allConfig + ) internal { + CrossChainRelay relay = CrossChainRelay(payable(relayAddress)); + + uint256[] memory chainIds = allConfig.getChainIds(); + + for (uint256 i = 0; i < chainIds.length; i++) { + uint256 chainId = chainIds[i]; + uint16 lzChainId = allConfig._chainId2LzIdMapping(chainId); + relay.addChainIdMapping(chainId, lzChainId); + } + bytes memory lzPath = abi.encodePacked(otherRelayAddress, relayAddress); + relay.setSrcChainId(srcChainId); + relay.setTrustedRemote(dstLzChainId, lzPath); + relay.addCaller(crossChainManagerAddress); + + transferRelay(relayAddress); + } + + function updateRelayOnCrossChainManager( + string memory currentSide, + address relayAddress, + address crossChainManagerAddress + ) internal { + if (currentSide.compare("vault")) { + VaultCrossChainManager vaultCrossChainManager = VaultCrossChainManager(payable(crossChainManagerAddress)); + vaultCrossChainManager.setCrossChainRelay(relayAddress); + } else { + LedgerCrossChainManager ledgerCrossChainManager = LedgerCrossChainManager(payable(crossChainManagerAddress)); + ledgerCrossChainManager.setCrossChainRelay(relayAddress); + } + } + + function updateOtherSideRelay(address relayAddress, address otherRelayAddress, uint16 dstLzChainId) internal { + CrossChainRelay relay = CrossChainRelay(payable(relayAddress)); + bytes memory lzPath = abi.encodePacked(otherRelayAddress, relayAddress); + relay.setTrustedRemote(dstLzChainId, lzPath); + } + + function initCrossChainManager( + string memory currentSide, + address relayAddress, + address crossChainManagerAddress, + address otherCrossChainManagerAddress, + uint256 srcChainId, + uint256 dstChainId + ) internal { + if (currentSide.compare("vault")) { + VaultCrossChainManager vaultCrossChainManager = VaultCrossChainManager(payable(crossChainManagerAddress)); + + vaultCrossChainManager.setChainId(srcChainId); + vaultCrossChainManager.setCrossChainRelay(relayAddress); + vaultCrossChainManager.setLedgerCrossChainManager(dstChainId, otherCrossChainManagerAddress); + } else { + LedgerCrossChainManager ledgerCrossChainManager = LedgerCrossChainManager(payable(crossChainManagerAddress)); + + ledgerCrossChainManager.setChainId(srcChainId); + ledgerCrossChainManager.setCrossChainRelay(relayAddress); + ledgerCrossChainManager.setVaultCrossChainManager(dstChainId, otherCrossChainManagerAddress); + } + } + + function updateCrossChainManagerOnRelay(address relayAddress, address crossChainManagerAddress) internal { + CrossChainRelay relay = CrossChainRelay(payable(relayAddress)); + relay.addCaller(crossChainManagerAddress); + } + + function updateOtherSideCrossChainManager( + string memory currentSide, + address crossChainManagerAddress, + address otherCrossChainManagerAddress, + uint256 dstChainId + ) internal { + if (currentSide.compare("vault")) { + VaultCrossChainManager vaultCrossChainManager = VaultCrossChainManager(payable(crossChainManagerAddress)); + vaultCrossChainManager.setLedgerCrossChainManager(dstChainId, otherCrossChainManagerAddress); + } else { + LedgerCrossChainManager ledgerCrossChainManager = LedgerCrossChainManager(payable(crossChainManagerAddress)); + ledgerCrossChainManager.setVaultCrossChainManager(dstChainId, otherCrossChainManagerAddress); + } + } + + function setGasMapping(string memory crosschainOption, CrossChainConfig allConfig, address relayAddress) internal { + //CrossChainConfig.ChainConfig memory config = allConfig.getChainConfig(crosschainOption); + + CrossChainRelay relay = CrossChainRelay(payable(relayAddress)); + + relay.addFlowGasLimitMapping(0, 3000000); + relay.addFlowGasLimitMapping(1, 3000000); + relay.addFlowGasLimitMapping(2, 3000000); + } + + function forceResumeReceive(address relayAddress, address otherRelayAddress, uint16 dstLzChainId) internal { + CrossChainRelay relay = CrossChainRelay(payable(relayAddress)); + bytes memory lzPath = abi.encodePacked(otherRelayAddress, relayAddress); + relay.forceResumeReceive(dstLzChainId, lzPath); + } + + function retryLzCrossChain(address relayAddress, address otherRelayAddress, address endpoint, uint16 dstLzChainId) + internal + { + bytes memory data = vm.envBytes("CROSS_CHAIN_PAYLOAD"); + ILayerZeroEndpoint endpointInstance = ILayerZeroEndpoint(payable(endpoint)); + ( + uint16 srcChainId, + bytes memory srcAddress, + address dstAddress, + uint64 nonce, + bytes memory payload, + bytes memory reason + ) = abi.decode(data, (uint16, bytes, address, uint64, bytes, bytes)); + bytes memory lzPath = abi.encodePacked(relayAddress, otherRelayAddress); + endpointInstance.retryPayload(srcChainId, srcAddress, payload); + } + + function getPrivateKey(string memory network) internal returns (uint256) { + if (network.compare("fuji")) { + return vm.envUint("FUJI_PRIVATE_KEY"); + } else if (network.compare("mumbai")) { + return vm.envUint("MUMBAI_PRIVATE_KEY"); + } else if (network.compare("orderly")) { + return vm.envUint("ORDERLY_PRIVATE_KEY"); + } else { + revert("Invalid network"); + } + } + + function getLzEndpoint(string memory network) internal returns (address) { + if (network.compare("fuji")) { + return vm.envAddress("FUJI_ENDPOINT"); + } else if (network.compare("mumbai")) { + return vm.envAddress("MUMBAI_ENDPOINT"); + } else if (network.compare("orderly")) { + return vm.envAddress("ORDERLY_ENDPOINT"); + } else { + revert("Invalid network"); + } + } + + function getCrossChainInfo(string memory currentSide, CrossChainConfig.ChainConfig memory config) + internal + pure + returns (address, address, address, address, string memory, uint256, uint16, uint256, uint16) + { + if (currentSide.compare("vault")) { + return ( + config.vaultRelay, + config.ledgerRelay, + config.vaultCrossChainManager, + config.ledgerCrossChainManager, + config.vaultNetwork, + config.vaultChainId, + config.vaultLzChainId, + config.ledgerChainId, + config.ledgerLzChainId + ); + } else if (currentSide.compare("ledger")) { + return ( + config.ledgerRelay, + config.vaultRelay, + config.ledgerCrossChainManager, + config.vaultCrossChainManager, + config.ledgerNetwork, + config.ledgerChainId, + config.ledgerLzChainId, + config.vaultChainId, + config.vaultLzChainId + ); + } else { + revert("Invalid currentSide"); + } + } +} diff --git a/myScript/CrossChainConfig.sol b/myScript/CrossChainConfig.sol new file mode 100644 index 0000000..70fce45 --- /dev/null +++ b/myScript/CrossChainConfig.sol @@ -0,0 +1,97 @@ +// SPDX-LICENSE-Identifier: MIT +pragma solidity ^0.8.17; + +import "./Utils.sol"; + +contract CrossChainConfig { + using StringCompare for string; + + mapping(uint256 => uint16) public _chainId2LzIdMapping; + mapping(string => ChainConfig) public _chainConfigMapping; + mapping(string => uint256) public _netork2ChainIdMapping; + uint256[] public _chainIds; + + struct ChainConfig { + string vaultNetwork; + string ledgerNetwork; + address ledgerCrossChainManager; + address vaultCrossChainManager; + address ledgerRelay; + address vaultRelay; + uint256 ledgerRelayTransfer; + uint256 vaultRelayTransfer; + uint256 ledgerChainId; + uint16 ledgerLzChainId; + uint256 vaultChainId; + uint16 vaultLzChainId; + bytes lzCrossChainPath; + } + + constructor() { + _chainId2LzIdMapping[986532] = 10174; + _chainId2LzIdMapping[43113] = 10106; + _chainId2LzIdMapping[80001] = 10109; + _chainIds.push(986532); + _chainIds.push(43113); + _chainIds.push(80001); + + _netork2ChainIdMapping["orderly"] = 986532; + _netork2ChainIdMapping["fuji"] = 43113; + _netork2ChainIdMapping["mumbai"] = 80001; + + _chainConfigMapping["fuji-mumbai"] = getFujiMumbaiChainConfig(); + _chainConfigMapping["fuji-orderly"] = getFujiOrderlyChainConfig(); + } + + function getChainConfig(string memory crosschainOption) public view returns (ChainConfig memory) { + return _chainConfigMapping[crosschainOption]; + } + + function getChainIds() public view returns (uint256[] memory) { + return _chainIds; + } + + function getFujiMumbaiChainConfig() internal pure returns (ChainConfig memory) { + address ledgerRelay = 0x160aeA20EdB575204849d91F7f3B7c150877a26A; + address vaultRelay = 0xc8E38C1Fd1422f49DB592BAe619080EA5Deb50e0; + return ChainConfig({ + vaultNetwork: "fuji", + ledgerNetwork: "mumbai", + ledgerCrossChainManager: 0x5771B915a19f1763274Ef97a475C4525dA7F963F, + vaultCrossChainManager: 0x339c8523d4c2354E392424D76C2c3546Df2e7a13, + ledgerRelay: ledgerRelay, + vaultRelay: vaultRelay, + // 2 native token (Mumbai) + ledgerRelayTransfer: 2_000_000_000_000_000_000, + // 1 native token (fuji) + vaultRelayTransfer: 1_000_000_000_000_000_000, + ledgerChainId: 80001, + ledgerLzChainId: 10109, + vaultChainId: 43113, + vaultLzChainId: 10106, + lzCrossChainPath: abi.encodePacked(ledgerRelay, vaultRelay) + }); + } + + function getFujiOrderlyChainConfig() internal pure returns (ChainConfig memory) { + address ledgerRelay = 0xD95832A046366C710c0dAfbF6c21f0A5ADAA6193; + address vaultRelay = 0xF88a250d55D7f94A9006B89E283700eB90FC7738; + return ChainConfig({ + vaultNetwork: "fuji", + ledgerNetwork: "orderly", + ledgerCrossChainManager: 0x5E212beC81Cc5D048B72E896897424034a0df6b6, + vaultCrossChainManager: 0x783744176804C59B2a25CE71d68A94713C73fD8E, + ledgerRelay: ledgerRelay, + vaultRelay: vaultRelay, + // 2 native token (Orderly) + ledgerRelayTransfer: 2_000_000_000_000_000_000, + // 1 native token (fuji) + vaultRelayTransfer: 1_000_000_000_000_000_000, + ledgerChainId: 986532, + ledgerLzChainId: 10174, + vaultChainId: 43113, + vaultLzChainId: 10106, + lzCrossChainPath: abi.encodePacked(ledgerRelay, vaultRelay) + }); + } +} diff --git a/myScript/DeployAndUpgradeManager.s.sol b/myScript/DeployAndUpgradeManager.s.sol new file mode 100644 index 0000000..d696be7 --- /dev/null +++ b/myScript/DeployAndUpgradeManager.s.sol @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +import "./BaseScript.s.sol"; + +import "../src/LedgerCrossChainManagerUpgradeable.sol"; +import "../src/VaultCrossChainManagerUpgradeable.sol"; +import "../src/CrossChainManagerProxy.sol"; + +contract DeployAndUpgradeManager is BaseScript { + using StringCompare for string; + + event Deployed(string contractName, address deployAddress); + + function run() external { + string memory network = vm.envString("CURRENT_NETWORK"); + string memory currentSide = vm.envString("CURRENT_SIDE"); + string memory method = vm.envString("CALL_METHOD"); + uint256 privateKey = getPrivateKey(network); + + vm.startBroadcast(privateKey); + + if (method.compare("deploy")) { + deploy(currentSide); + } else if (method.compare("upgrade")) { + upgrade(currentSide, network); + } else { + revert("Invalid method"); + } + vm.stopBroadcast(); + } + + function deploy(string memory side) internal { + if (side.compare("ledger")) { + deployLedgerManager(); + } else if (side.compare("vault")) { + deployVaultManager(); + } else { + revert("Invalid side"); + } + } + + function upgrade(string memory side, string memory network) internal { + address proxyAddress = getManagerProxyAddress(network); + if (side.compare("ledger")) { + upgradeLedgerManager(proxyAddress); + } else if (side.compare("vault")) { + upgradeVaultManager(proxyAddress); + } else { + revert("Invalid side"); + } + } + + function deployVaultManager() internal { + // deploy vault manager + VaultCrossChainManagerUpgradeable manager = new VaultCrossChainManagerUpgradeable(); + // deploy proxy + CrossChainManagerProxy proxy = new CrossChainManagerProxy(address(manager), ""); + // initialize proxy + VaultCrossChainManagerUpgradeable proxyManager = VaultCrossChainManagerUpgradeable(payable(address(proxy))); + proxyManager.initialize(); + + // logging deploy address + emit Deployed("VaultCrossChainManagerUpgradeable", address(manager)); + emit Deployed("CrossChainManagerProxy", address(proxy)); + } + + function deployLedgerManager() internal { + // deploy ledger manager + LedgerCrossChainManagerUpgradeable manager = new LedgerCrossChainManagerUpgradeable(); + // deploy proxy + CrossChainManagerProxy proxy = new CrossChainManagerProxy(address(manager), ""); + // initialize proxy + LedgerCrossChainManagerUpgradeable proxyManager = LedgerCrossChainManagerUpgradeable(payable(address(proxy))); + proxyManager.initialize(); + + // logging deploy address + emit Deployed("LedgerCrossChainManagerUpgradeable", address(manager)); + emit Deployed("CrossChainManagerProxy", address(proxy)); + } + + function upgradeVaultManager(address proxyAddress) internal { + // deploy new manager + VaultCrossChainManagerUpgradeable newManager = new VaultCrossChainManagerUpgradeable(); + // upgrade proxy + VaultCrossChainManagerUpgradeable proxyManager = VaultCrossChainManagerUpgradeable(payable(proxyAddress)); + proxyManager.upgradeTo(address(newManager)); + + // logging deploy address + emit Deployed("VaultCrossChainManagerUpgradeable", address(newManager)); + } + + function upgradeLedgerManager(address proxyAddress) internal { + // deploy new manager + LedgerCrossChainManagerUpgradeable newManager = new LedgerCrossChainManagerUpgradeable(); + // upgrade proxy + LedgerCrossChainManagerUpgradeable proxyManager = LedgerCrossChainManagerUpgradeable(payable(proxyAddress)); + proxyManager.upgradeTo(address(newManager)); + + // logging deploy address + emit Deployed("LedgerCrossChainManagerUpgradeable", address(newManager)); + } +} \ No newline at end of file diff --git a/myScript/DepositCrossChainFlow.s.sol b/myScript/DepositCrossChainFlow.s.sol new file mode 100644 index 0000000..989b87a --- /dev/null +++ b/myScript/DepositCrossChainFlow.s.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "forge-std/Script.sol"; +import "../lib/crosschain/contracts/CrossChainRelay.sol"; +import "../src/VaultCrossChainManager.sol"; +import "../src/library/types/VaultTypes.sol"; + +contract DepositCrossChainVaultSide is Script{ + function run() external { + //uint256 orderlyPrivateKey = vm.envUint("ORDERLY_PRIVATE_KEY"); + uint256 fujiPrivateKey = vm.envUint("FUJI_PRIVATE_KEY"); + // address ledgerRelay = vm.envAddress("LEDGER_RELAY_ADDRESS"); + // address vaultRelay = vm.envAddress("VAULT_RELAY_ADDRESS"); + // address ledgerCrossChainManager = vm.envAddress("LEDGER_CROSS_CHAIN_MANAGER_ADDRESS"); + address vaultCrossChainManager = vm.envAddress("VAULT_CROSS_CHAIN_MANAGER_ADDRESS"); + // uint256 vaultSideChainId = 43113; + // uint256 ledgerSideChainId = 986532; + // uint16 vaultLzChainId = 10106; + // uint16 ledgerLzChainId = 10174; + // 100 native token + // uint256 ledgerTransferAmount = 100_000_000_000_000_000_000; + // 2 native token + // uint256 vaultTransferAmount = 1_000_000_000_000_000_000; + + vm.startBroadcast(fujiPrivateKey); + + VaultTypes.VaultDeposit memory data = VaultTypes.VaultDeposit({ + accountId: bytes32("abc"), + userAddress: address(0), + brokerHash: bytes32(0), + tokenHash: bytes32(0), + tokenAmount: 100, + depositNonce: 0 + }); + + VaultCrossChainManager vaultCrossChainManagerInstance = VaultCrossChainManager(payable(vaultCrossChainManager)); + vaultCrossChainManagerInstance.deposit(data); + + vm.stopBroadcast(); + } +} diff --git a/myScript/DepositCrossChainLedgerMumbai.s.sol b/myScript/DepositCrossChainLedgerMumbai.s.sol new file mode 100644 index 0000000..3ff7f34 --- /dev/null +++ b/myScript/DepositCrossChainLedgerMumbai.s.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "forge-std/Script.sol"; +import "../lib/crosschain/contracts/CrossChainRelay.sol"; +import "../src/LedgerCrossChainManager.sol"; + +contract DepositCrossChainLedgerMumbai is Script{ + function run() external { + //uint256 orderlyPrivateKey = vm.envUint("ORDERLY_PRIVATE_KEY"); + //uint256 fujiPrivateKey = vm.envUint("FUJI_PRIVATE_KEY"); + uint256 mumbaiPrivateKey = vm.envUint("MUMBAI_PRIVATE_KEY"); + //address ledgerRelay = vm.envAddress("LEDGER_RELAY_ADDRESS"); + address mumbaiLedgerRelay = vm.envAddress("MUMBAI_LEDGER_RELAY_ADDRESS"); + address vaultRelay = vm.envAddress("VAULT_RELAY_ADDRESS"); + //address ledgerCrossChainManager = vm.envAddress("LEDGER_CROSS_CHAIN_MANAGER_ADDRESS"); + address mumbaiLedgerCrossChainManager = vm.envAddress("MUMBAI_LEDGER_CROSS_CHAIN_MANAGER_ADDRESS"); + address vaultCrossChainManager = vm.envAddress("VAULT_CROSS_CHAIN_MANAGER_ADDRESS"); + uint256 vaultSideChainId = 43113; + uint256 ledgerSideChainId = 986532; + uint256 mumbaiLedgerChainId = 80001; + uint16 vaultLzChainId = 10106; + uint16 ledgerLzChainId = 10174; + uint16 mumbaiLedgerLzChainId = 10109; + // 100 native token + //uint256 ledgerTransferAmount = 100_000_000_000_000_000_000; + // 2 native token + //uint256 vaultTransferAmount = 2_000_000_000_000_000_000; + // 2 native token + uint256 mumbaiLedgerTransferAmount = 2_000_000_000_000_000_000; + + vm.startBroadcast(mumbaiPrivateKey); + + CrossChainRelay relay = CrossChainRelay(payable(mumbaiLedgerRelay)); + + bytes memory ledgerLzPath = abi.encodePacked(vaultRelay, mumbaiLedgerRelay); + + relay.addChainIdMapping(ledgerSideChainId, ledgerLzChainId); + relay.addChainIdMapping(vaultSideChainId, vaultLzChainId); + relay.addChainIdMapping(mumbaiLedgerChainId, mumbaiLedgerLzChainId); + relay.setSrcChainId(mumbaiLedgerChainId); + relay.setTrustedRemote(vaultLzChainId, ledgerLzPath); + relay.addCaller(mumbaiLedgerCrossChainManager); + + payable(mumbaiLedgerRelay).call{value: mumbaiLedgerTransferAmount}(""); + + LedgerCrossChainManager ledgerCrossChainManagerInstance = LedgerCrossChainManager(payable(mumbaiLedgerCrossChainManager)); + ledgerCrossChainManagerInstance.setChainId(mumbaiLedgerChainId); + ledgerCrossChainManagerInstance.setCrossChainRelay(mumbaiLedgerRelay); + ledgerCrossChainManagerInstance.setVaultCrossChainManager(vaultSideChainId, vaultCrossChainManager); + + vm.stopBroadcast(); + } +} diff --git a/myScript/DepositCrossChainLedgerSide.s.sol b/myScript/DepositCrossChainLedgerSide.s.sol new file mode 100644 index 0000000..15aa608 --- /dev/null +++ b/myScript/DepositCrossChainLedgerSide.s.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "forge-std/Script.sol"; +import "../lib/crosschain/contracts/CrossChainRelay.sol"; +import "../src/LedgerCrossChainManager.sol"; + +contract DepositCrossChainLedgerSide is Script{ + function run() external { + uint256 orderlyPrivateKey = vm.envUint("ORDERLY_PRIVATE_KEY"); + //uint256 fujiPrivateKey = vm.envUint("FUJI_PRIVATE_KEY"); + //uint256 mumbaiPrivateKey = vm.envUint("MUMBAI_PRIVATE_KEY"); + address ledgerRelay = vm.envAddress("LEDGER_RELAY_ADDRESS"); + address vaultRelay = vm.envAddress("VAULT_RELAY_ADDRESS"); + address ledgerCrossChainManager = vm.envAddress("LEDGER_CROSS_CHAIN_MANAGER_ADDRESS"); + address vaultCrossChainManager = vm.envAddress("VAULT_CROSS_CHAIN_MANAGER_ADDRESS"); + uint256 vaultSideChainId = 43113; + uint256 ledgerSideChainId = 986532; + //uint256 mumbaiLedgerChainId = 80001; + uint16 vaultLzChainId = 10106; + uint16 ledgerLzChainId = 10174; + //uint16 mumbaiLedgerLzChainId = 10109; + // 100 native token + uint256 ledgerTransferAmount = 100_000_000_000_000_000_000; + // 2 native token + //uint256 vaultTransferAmount = 2_000_000_000_000_000_000; + + vm.startBroadcast(orderlyPrivateKey); + + CrossChainRelay relay = CrossChainRelay(payable(ledgerRelay)); + + bytes memory ledgerLzPath = abi.encodePacked(vaultRelay, ledgerRelay); + + relay.addChainIdMapping(ledgerSideChainId, ledgerLzChainId); + relay.addChainIdMapping(vaultSideChainId, vaultLzChainId); + relay.setSrcChainId(ledgerSideChainId); + relay.setTrustedRemote(vaultLzChainId, ledgerLzPath); + relay.addCaller(ledgerCrossChainManager); + + payable(ledgerRelay).call{value: ledgerTransferAmount}(""); + + LedgerCrossChainManager ledgerCrossChainManagerInstance = LedgerCrossChainManager(payable(ledgerCrossChainManager)); + ledgerCrossChainManagerInstance.setChainId(ledgerSideChainId); + ledgerCrossChainManagerInstance.setCrossChainRelay(ledgerRelay); + ledgerCrossChainManagerInstance.setVaultCrossChainManager(vaultSideChainId, vaultCrossChainManager); + + vm.stopBroadcast(); + } +} diff --git a/myScript/DepositCrossChainVaultSide.s.sol b/myScript/DepositCrossChainVaultSide.s.sol new file mode 100644 index 0000000..ea47a05 --- /dev/null +++ b/myScript/DepositCrossChainVaultSide.s.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "forge-std/Script.sol"; +import "../lib/crosschain/contracts/CrossChainRelay.sol"; +import "../src/VaultCrossChainManager.sol"; + +contract DepositCrossChainVaultSide is Script{ + function run() external { + //uint256 orderlyPrivateKey = vm.envUint("ORDERLY_PRIVATE_KEY"); + uint256 fujiPrivateKey = vm.envUint("FUJI_PRIVATE_KEY"); + //address mumbaiLedgerRelay = vm.envAddress("MUMBAI_LEDGER_RELAY_ADDRESS"); + address ledgerRelay = vm.envAddress("LEDGER_RELAY_ADDRESS"); + address vaultRelay = vm.envAddress("VAULT_RELAY_ADDRESS"); + address ledgerCrossChainManager = vm.envAddress("LEDGER_CROSS_CHAIN_MANAGER_ADDRESS"); + //address mumbaiLedgerCrossChainManager = vm.envAddress("MUMBAI_LEDGER_CROSS_CHAIN_MANAGER_ADDRESS"); + address vaultCrossChainManager = vm.envAddress("VAULT_CROSS_CHAIN_MANAGER_ADDRESS"); + uint256 vaultSideChainId = 43113; + uint256 ledgerSideChainId = 986532; + //uint256 mumbaiLedgerChainId = 80001; + uint16 vaultLzChainId = 10106; + uint16 ledgerLzChainId = 10174; + //uint16 mumbaiLedgerLzChainId = 10109; + // 100 native token + //uint256 ledgerTransferAmount = 100_000_000_000_000_000_000; + // 1 native token + uint256 vaultTransferAmount = 1_000_000_000_000_000_000; + + vm.startBroadcast(fujiPrivateKey); + + CrossChainRelay relay = CrossChainRelay(payable(vaultRelay)); + + //bytes memory vaultLzPath = abi.encodePacked(mumbaiLedgerRelay, vaultRelay); + bytes memory vaultLzPath = abi.encodePacked(ledgerRelay, vaultRelay); + + relay.addChainIdMapping(ledgerSideChainId, ledgerLzChainId); + relay.addChainIdMapping(vaultSideChainId, vaultLzChainId); + //relay.addChainIdMapping(mumbaiLedgerChainId, mumbaiLedgerLzChainId); + relay.setSrcChainId(vaultSideChainId); + relay.setTrustedRemote(ledgerLzChainId, vaultLzPath); + //relay.setTrustedRemote(mumbaiLedgerLzChainId, vaultLzPath); + relay.addCaller(vaultCrossChainManager); + + //payable(vaultRelay).call{value: vaultTransferAmount}(""); + + VaultCrossChainManager vaultCrossChainManagerInstance = VaultCrossChainManager(payable(vaultCrossChainManager)); + vaultCrossChainManagerInstance.setChainId(vaultSideChainId); + vaultCrossChainManagerInstance.setCrossChainRelay(vaultRelay); + //vaultCrossChainManagerInstance.setLedgerCrossChainManager(mumbaiLedgerChainId, mumbaiLedgerCrossChainManager); + vaultCrossChainManagerInstance.setLedgerCrossChainManager(ledgerSideChainId, ledgerCrossChainManager); + + vm.stopBroadcast(); + } +} diff --git a/myScript/RelaySetGasVault.s.sol b/myScript/RelaySetGasVault.s.sol new file mode 100644 index 0000000..5a3e8f0 --- /dev/null +++ b/myScript/RelaySetGasVault.s.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "forge-std/Script.sol"; +import "../lib/crosschain/contracts/CrossChainRelay.sol"; +import "../src/VaultCrossChainManager.sol"; + +contract RelayWithdrawVault is Script{ + function run() external { + //uint256 orderlyPrivateKey = vm.envUint("ORDERLY_PRIVATE_KEY"); + uint256 fujiPrivateKey = vm.envUint("FUJI_PRIVATE_KEY"); + //address ledgerRelay = vm.envAddress("LEDGER_RELAY_ADDRESS"); + address vaultRelay = vm.envAddress("VAULT_RELAY_ADDRESS"); + //address ledgerCrossChainManager = vm.envAddress("LEDGER_CROSS_CHAIN_MANAGER_ADDRESS"); + //address vaultCrossChainManager = vm.envAddress("VAULT_CROSS_CHAIN_MANAGER_ADDRESS"); + // uint256 vaultSideChainId = 43113; + // uint256 ledgerSideChainId = 986532; + // uint16 vaultLzChainId = 10106; + // uint16 ledgerLzChainId = 10174; + // // 100 native token + // uint256 ledgerTransferAmount = 100_000_000_000_000_000_000; + // // 2 native token + // uint256 vaultTransferAmount = 1_000_000_000_000_000_000; + + vm.startBroadcast(fujiPrivateKey); + + // address signerAddress = vm.addr(fujiPrivateKey); + + CrossChainRelay relay = CrossChainRelay(payable(vaultRelay)); + + relay.addFlowGasLimitMapping(0, 3000000); + relay.addFlowGasLimitMapping(1, 3000000); + relay.addFlowGasLimitMapping(2, 3000000); + + vm.stopBroadcast(); + } +} diff --git a/myScript/RelayWithdrawLedger.s.sol b/myScript/RelayWithdrawLedger.s.sol new file mode 100644 index 0000000..e779fa7 --- /dev/null +++ b/myScript/RelayWithdrawLedger.s.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "forge-std/Script.sol"; +import "../lib/crosschain/contracts/CrossChainRelay.sol"; +import "../src/VaultCrossChainManager.sol"; + +contract RelayWithdrawVault is Script{ + function run() external { + uint256 orderlyPrivateKey = vm.envUint("ORDERLY_PRIVATE_KEY"); + // uint256 fujiPrivateKey = vm.envUint("FUJI_PRIVATE_KEY"); + address ledgerRelay = vm.envAddress("LEDGER_RELAY_ADDRESS"); + // address vaultRelay = vm.envAddress("VAULT_RELAY_ADDRESS"); + // address ledgerCrossChainManager = vm.envAddress("LEDGER_CROSS_CHAIN_MANAGER_ADDRESS"); + // address vaultCrossChainManager = vm.envAddress("VAULT_CROSS_CHAIN_MANAGER_ADDRESS"); + // uint256 vaultSideChainId = 43113; + // uint256 ledgerSideChainId = 986532; + // uint16 vaultLzChainId = 10106; + // uint16 ledgerLzChainId = 10174; + // 100 native token + uint256 ledgerTransferAmount = 100_000_000_000_000_000_000; + // 2 native token + // uint256 vaultTransferAmount = 1_000_000_000_000_000_000; + + vm.startBroadcast(orderlyPrivateKey); + + address signerAddress = vm.addr(orderlyPrivateKey); + + CrossChainRelay relay = CrossChainRelay(payable(ledgerRelay)); + + relay.withdrawNativeToken(payable(signerAddress), ledgerTransferAmount); + + vm.stopBroadcast(); + } +} diff --git a/myScript/RelayWithdrawLedgerMumbai.s.sol b/myScript/RelayWithdrawLedgerMumbai.s.sol new file mode 100644 index 0000000..d734911 --- /dev/null +++ b/myScript/RelayWithdrawLedgerMumbai.s.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "forge-std/Script.sol"; +import "../lib/crosschain/contracts/CrossChainRelay.sol"; +import "../src/VaultCrossChainManager.sol"; + +contract RelayWithdrawLedgerMumbai is Script{ + function run() external { + //uint256 orderlyPrivateKey = vm.envUint("ORDERLY_PRIVATE_KEY"); + uint256 mumbaiPrivateKey = vm.envUint("MUMBAI_PRIVATE_KEY"); + // uint256 fujiPrivateKey = vm.envUint("FUJI_PRIVATE_KEY"); + address ledgerRelay = vm.envAddress("MUMBAI_LEDGER_RELAY_ADDRESS"); + // address vaultRelay = vm.envAddress("VAULT_RELAY_ADDRESS"); + // address ledgerCrossChainManager = vm.envAddress("LEDGER_CROSS_CHAIN_MANAGER_ADDRESS"); + // address vaultCrossChainManager = vm.envAddress("VAULT_CROSS_CHAIN_MANAGER_ADDRESS"); + // uint256 vaultSideChainId = 43113; + // uint256 ledgerSideChainId = 986532; + // uint16 vaultLzChainId = 10106; + // uint16 ledgerLzChainId = 10174; + // 100 native token + // uint256 ledgerTransferAmount = 100_000_000_000_000_000_000; + // 2 native token + // uint256 vaultTransferAmount = 1_000_000_000_000_000_000; + // 100 native token + uint256 mumbaiLedgerTransferAmount = 2_000_000_000_000_000_000; + + vm.startBroadcast(mumbaiPrivateKey); + + address signerAddress = vm.addr(mumbaiPrivateKey); + + CrossChainRelay relay = CrossChainRelay(payable(ledgerRelay)); + + relay.withdrawNativeToken(payable(signerAddress), mumbaiLedgerTransferAmount); + + vm.stopBroadcast(); + } +} diff --git a/myScript/RelayWithdrawVault.s.sol b/myScript/RelayWithdrawVault.s.sol new file mode 100644 index 0000000..038f371 --- /dev/null +++ b/myScript/RelayWithdrawVault.s.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "forge-std/Script.sol"; +import "../lib/crosschain/contracts/CrossChainRelay.sol"; +import "../src/VaultCrossChainManager.sol"; + +contract RelayWithdrawVault is Script{ + function run() external { + uint256 orderlyPrivateKey = vm.envUint("ORDERLY_PRIVATE_KEY"); + uint256 fujiPrivateKey = vm.envUint("FUJI_PRIVATE_KEY"); + address ledgerRelay = vm.envAddress("LEDGER_RELAY_ADDRESS"); + address vaultRelay = vm.envAddress("VAULT_RELAY_ADDRESS"); + address ledgerCrossChainManager = vm.envAddress("LEDGER_CROSS_CHAIN_MANAGER_ADDRESS"); + address vaultCrossChainManager = vm.envAddress("VAULT_CROSS_CHAIN_MANAGER_ADDRESS"); + uint256 vaultSideChainId = 43113; + uint256 ledgerSideChainId = 986532; + uint16 vaultLzChainId = 10106; + uint16 ledgerLzChainId = 10174; + // 100 native token + uint256 ledgerTransferAmount = 100_000_000_000_000_000_000; + // 2 native token + uint256 vaultTransferAmount = 1_055_000_000_000_000_000; + + vm.startBroadcast(fujiPrivateKey); + + address signerAddress = vm.addr(fujiPrivateKey); + + CrossChainRelay relay = CrossChainRelay(payable(vaultRelay)); + + relay.withdrawNativeToken(payable(signerAddress), vaultTransferAmount); + + vm.stopBroadcast(); + } +} diff --git a/myScript/SetupManager.s.sol b/myScript/SetupManager.s.sol new file mode 100644 index 0000000..7f195ff --- /dev/null +++ b/myScript/SetupManager.s.sol @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +import "./BaseScript.s.sol"; + +import "../src/LedgerCrossChainManagerUpgradeable.sol"; +import "../src/VaultCrossChainManagerUpgradeable.sol"; +import "../src/CrossChainManagerProxy.sol"; + +contract SetupManager is BaseScript { + using StringCompare for string; + + function run() external { + string memory network = vm.envString("CURRENT_NETWORK"); + string memory currentSide = vm.envString("CURRENT_SIDE"); + string memory method = vm.envString("CALL_METHOD"); + uint256 privateKey = getPrivateKey(network); + + vm.startBroadcast(privateKey); + + if (method.compare("setup")) { + address managerAddress = getManagerProxyAddress(network); + address relayAddress = getRelayProxyAddress(network); + uint256 chainId = getChainId(network); + if (currentSide.compare("ledger")) { + address operatorAddress = getOperatorManagerAddress(network); + address ledgerAddress = getLedgerAddress(network); + console.log("ledgerAddress: ", ledgerAddress); + console.log("operatorAddress: ", operatorAddress); + console.log("relayAddress: ", relayAddress); + console.log("managerAddress: ", managerAddress); + console.log("chainId: ", chainId); + ledgerSetup(managerAddress, relayAddress, operatorAddress, ledgerAddress, chainId); + } else if (currentSide.compare("vault")) { + string memory ledgerNetwork = vm.envString("LEDGER_NETWORK"); + uint256 ledgerChainId = getChainId(ledgerNetwork); + address ledgerManagerAddress = getManagerProxyAddress(ledgerNetwork); + address vaultAddress = getVaultAddress(network); + vaultSetup(managerAddress, relayAddress, ledgerManagerAddress, vaultAddress, chainId, ledgerChainId); + } else { + revert("Invalid side"); + } + } else if (method.compare("addVaultOnLedger")) { + string memory addVaultNetwork = vm.envString("ADD_VAULT_NETWORK"); + address vaultManagerAddress = getManagerProxyAddress(addVaultNetwork); + uint256 vaultChainId = getChainId(addVaultNetwork); + address managerAddress = getManagerProxyAddress(network); + addVaultManagerToLedgerManager(managerAddress, vaultManagerAddress, vaultChainId); + } else if (method.compare("setLedgerOnVault")) { + setLedgerOnVault(network); + } else if (method.compare("test")) { + sendTestWithdraw(network); + } else { + revert("Invalid method"); + } + + vm.stopBroadcast(); + } + + function vaultSetup( + address managerAddress, + address relayAddress, + address ledgerManagerAddress, + address vaultAddress, + uint256 chainId, + uint256 ledgerChainId + ) internal { + VaultCrossChainManagerUpgradeable manager = VaultCrossChainManagerUpgradeable(payable(managerAddress)); + manager.setChainId(chainId); + manager.setCrossChainRelay(relayAddress); + manager.setLedgerCrossChainManager(ledgerChainId, ledgerManagerAddress); + manager.setVault(vaultAddress); + } + + function ledgerSetup( + address managerAddress, + address relayAddress, + address operatorAddress, + address ledgerAddress, + uint256 chainId + ) internal { + console.log("managerAddress: ", managerAddress); + console.log("start ledger setup"); + LedgerCrossChainManagerUpgradeable manager = LedgerCrossChainManagerUpgradeable(payable(managerAddress)); + manager.setChainId(chainId); + console.log("chainId: ", chainId); + manager.setCrossChainRelay(relayAddress); + console.log("relayAddress: ", relayAddress); + manager.setOperatorManager(operatorAddress); + console.log("operatorAddress: ", operatorAddress); + manager.setLedger(ledgerAddress); + console.log("ledgerAddress: ", ledgerAddress); + + // subnet USDC decimal + manager.setTokenDecimal(0xd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa, 986532, 6); + manager.setTokenDecimal(0xd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa, 43113, 4); + // manager.setTokenDecimal(0xd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa, 4460, 6); + // manager.setTokenDecimal(0xd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa, 986533, 4); + // manager.setTokenDecimal(0xd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa, 986534, 6); + // fuji USDC decimal + // manager.setTokenDecimal(0xd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa, 421613, 6); + } + + function addVaultManagerToLedgerManager( + address ledgerManagerAddress, + address vaultManagerAddress, + uint256 vaultChainId + ) internal { + LedgerCrossChainManagerUpgradeable ledgerManager = + LedgerCrossChainManagerUpgradeable(payable(ledgerManagerAddress)); + ledgerManager.setVaultCrossChainManager(vaultChainId, vaultManagerAddress); + } + + function sendTestWithdraw(string memory network) internal { + address managerProxy = getManagerProxyAddress(network); + LedgerCrossChainManagerUpgradeable manager = LedgerCrossChainManagerUpgradeable(payable(managerProxy)); + + uint256 dstChainId = getChainId(vm.envString("TARGET_NETWORK")); + manager.sendTestWithdraw(dstChainId); + } + + function setLedgerOnVault(string memory network) internal { + string memory ledgerNetwork = vm.envString("LEDGER_NETWORK"); + address ledgerProxy = getManagerProxyAddress(ledgerNetwork); + address vaultProxy = getManagerProxyAddress(network); + uint256 ledgerChainId = getChainId(ledgerNetwork); + + VaultCrossChainManagerUpgradeable vaultManager = VaultCrossChainManagerUpgradeable(payable(vaultProxy)); + vaultManager.setLedgerCrossChainManager(ledgerChainId, ledgerProxy); + } +} diff --git a/myScript/Utils.sol b/myScript/Utils.sol new file mode 100644 index 0000000..09ad34e --- /dev/null +++ b/myScript/Utils.sol @@ -0,0 +1,9 @@ +// SPDX-LICENSE-Identifier: MIT +pragma solidity ^0.8.17; + + +library StringCompare { + function compare(string memory a, string memory b) internal pure returns (bool) { + return (keccak256(abi.encodePacked((a))) == keccak256(abi.encodePacked((b)))); + } +} \ No newline at end of file diff --git a/paramrc b/paramrc new file mode 100644 index 0000000..2bdff44 --- /dev/null +++ b/paramrc @@ -0,0 +1,3 @@ +RELAY_TRANSFER_AMOUNT=1000000000000000000 +CROSS_CHAIN_SCRIPT_METHOD="init" +CROSS_CHAIN_CURRENT_SIDE="vault" \ No newline at end of file diff --git a/remappings.txt b/remappings.txt index 5bc6932..c84a082 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1,5 +1,5 @@ -create3-factory/=lib/create3-factory/ ds-test/=lib/forge-std/lib/ds-test/src/ forge-std/=lib/forge-std/src/ openzeppelin-contracts/=lib/openzeppelin-contracts/ -solmate/=lib/solmate/src/ \ No newline at end of file +openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/ +contract-evm/=./ diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol deleted file mode 100644 index 8522f38..0000000 --- a/script/Deploy.s.sol +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.18; - -import {CREATE3Script} from "./base/CREATE3Script.sol"; -import {Vault} from "../src/vault.sol"; - -contract DeployScript is CREATE3Script { - constructor() CREATE3Script(vm.envString("VERSION")) {} - - function run() external returns (Vault a) { - uint256 deployerPrivateKey = uint256(vm.envBytes32("PRIVATE_KEY")); - - uint256 param = 123; - - vm.startBroadcast(deployerPrivateKey); - - a = Vault( - create3.deploy(getCreate3ContractSalt("Vault"), bytes.concat(type(Vault).creationCode, abi.encode(param))) - ); - - vm.stopBroadcast(); - } -} diff --git a/script/base/CREATE3Script.sol b/script/base/CREATE3Script.sol deleted file mode 100644 index ed61437..0000000 --- a/script/base/CREATE3Script.sol +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.18; - -import {CREATE3Factory} from "create3-factory/src/CREATE3Factory.sol"; - -import "forge-std/Script.sol"; - -abstract contract CREATE3Script is Script { - CREATE3Factory internal constant create3 = CREATE3Factory(0x9fBB3DF7C40Da2e5A0dE984fFE2CCB7C47cd0ABf); - - string internal version; - - constructor(string memory version_) { - version = version_; - } - - function getCreate3Contract(string memory name) internal view virtual returns (address) { - uint256 deployerPrivateKey = uint256(vm.envBytes32("PRIVATE_KEY")); - address deployer = vm.addr(deployerPrivateKey); - - return create3.getDeployed(deployer, getCreate3ContractSalt(name)); - } - - function getCreate3ContractSalt(string memory name) internal view virtual returns (bytes32) { - return keccak256(bytes(string.concat(name, "-v", version))); - } -} diff --git a/script/ledger/DeployProxyLedger.s.sol b/script/ledger/DeployProxyLedger.s.sol new file mode 100644 index 0000000..0bda759 --- /dev/null +++ b/script/ledger/DeployProxyLedger.s.sol @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "forge-std/Script.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; +import "../../src/OperatorManager.sol"; +import "../../src/Ledger.sol"; +import "../../src/VaultManager.sol"; +import "../../src/FeeManager.sol"; +import "../../src/MarketManager.sol"; + +contract DeployLedger is Script { + // bytes32 constant BROKER_HASH = 0x083098c593f395bea1de45dda552d9f14e8fcb0be3faaa7a1903c5477d7ba7fd; // woofi_dex + // bytes32 constant TOKEN_HASH = 0xd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa; // USDC + // uint256 constant CHAIN_ID = 421613; // Arbitrum Goerli + + function run() external { + uint256 orderlyPrivateKey = vm.envUint("ORDERLY_PRIVATE_KEY"); + + vm.startBroadcast(orderlyPrivateKey); + + ProxyAdmin admin = new ProxyAdmin(); + + IOperatorManager operatorManagerImpl = new OperatorManager(); + IVaultManager vaultManagerImpl = new VaultManager(); + ILedger ledgerImpl = new Ledger(); + IFeeManager feeImpl = new FeeManager(); + IMarketManager marketImpl = new MarketManager(); + + bytes memory initData = abi.encodeWithSignature("initialize()"); + + TransparentUpgradeableProxy operatorProxy = + new TransparentUpgradeableProxy(address(operatorManagerImpl), address(admin), initData); + TransparentUpgradeableProxy vaultProxy = + new TransparentUpgradeableProxy(address(vaultManagerImpl), address(admin), initData); + TransparentUpgradeableProxy ledgerProxy = + new TransparentUpgradeableProxy(address(ledgerImpl), address(admin), initData); + TransparentUpgradeableProxy feeProxy = + new TransparentUpgradeableProxy(address(feeImpl), address(admin), initData); + TransparentUpgradeableProxy marketProxy = + new TransparentUpgradeableProxy(address(marketImpl), address(admin), initData); + + IOperatorManager operatorManager = IOperatorManager(address(operatorProxy)); + IVaultManager vaultManager = IVaultManager(address(vaultProxy)); + ILedger ledger = ILedger(address(ledgerProxy)); + IFeeManager feeManager = IFeeManager(address(feeProxy)); + IMarketManager marketManager = IMarketManager(address(marketProxy)); + + // avoid stack too deep error + { + address operatorAdminAddress = vm.envAddress("OPERATOR_ADMIN_ADDRESS"); + address ledgerCrossChainManagerAddress = vm.envAddress("LEDGER_CROSS_CHAIN_MANAGER_ADDRESS"); + ILedgerCrossChainManager ledgerCrossChainManager = ILedgerCrossChainManager(ledgerCrossChainManagerAddress); + + ledger.setOperatorManagerAddress(address(operatorManager)); + ledger.setCrossChainManager(address(ledgerCrossChainManager)); + ledger.setVaultManager(address(vaultManager)); + ledger.setFeeManager(address(feeManager)); + ledger.setMarketManager(address(marketManager)); + + operatorManager.setOperator(operatorAdminAddress); + operatorManager.setLedger(address(ledger)); + operatorManager.setMarketManager(address(marketManager)); + + vaultManager.setLedgerAddress(address(ledger)); + // vaultManager.setAllowedBroker(BROKER_HASH, true); + // vaultManager.setAllowedToken(TOKEN_HASH, true); + // vaultManager.setAllowedChainToken(TOKEN_HASH, CHAIN_ID, true); + // vaultManager.setAllowedSymbol(SYMBOL_HASH, true); + + feeManager.setLedgerAddress(address(ledger)); + // feeManager.changeFeeCollector(1, address(0x1)); + // feeManager.changeFeeCollector(2, address(0x2)); + // feeManager.changeFeeCollector(3, address(0x3)); + + marketManager.setOperatorManagerAddress(address(operatorManager)); + marketManager.setLedgerAddress(address(ledger)); + + // ledgerCrossChainManager.setLedger(address(ledger)); + } + vm.stopBroadcast(); + } +} diff --git a/script/ledger/UpgradeFeeManager.s.sol b/script/ledger/UpgradeFeeManager.s.sol new file mode 100644 index 0000000..962a2d1 --- /dev/null +++ b/script/ledger/UpgradeFeeManager.s.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "forge-std/Script.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; +import "../../src/FeeManager.sol"; + +contract UpgradeFeeManager is Script { + function run() external { + uint256 orderlyPrivateKey = vm.envUint("ORDERLY_PRIVATE_KEY"); + address adminAddress = vm.envAddress("LEDGER_PROXY_ADMIN"); + address feeManagerAddress = vm.envAddress("FEE_MANAGER_ADDRESS"); + + ProxyAdmin admin = ProxyAdmin(adminAddress); + ITransparentUpgradeableProxy feeManagerProxy = ITransparentUpgradeableProxy(feeManagerAddress); + + vm.startBroadcast(orderlyPrivateKey); + + IFeeManager feeManagerImpl = new FeeManager(); + admin.upgrade(feeManagerProxy, address(feeManagerImpl)); + // admin.upgradeAndCall(feeManagerProxy, address(feeManagerImpl), abi.encodeWithSignature("initialize()")); + + vm.stopBroadcast(); + } +} diff --git a/script/ledger/UpgradeLedger.s.sol b/script/ledger/UpgradeLedger.s.sol new file mode 100644 index 0000000..19cbc27 --- /dev/null +++ b/script/ledger/UpgradeLedger.s.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "forge-std/Script.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; +import "../../src/Ledger.sol"; + +contract UpgradeLedger is Script { + function run() external { + uint256 orderlyPrivateKey = vm.envUint("ORDERLY_PRIVATE_KEY"); + address adminAddress = vm.envAddress("LEDGER_PROXY_ADMIN"); + address ledgerAddress = vm.envAddress("LEDGER_ADDRESS"); + + ProxyAdmin admin = ProxyAdmin(adminAddress); + ITransparentUpgradeableProxy ledgerProxy = ITransparentUpgradeableProxy(ledgerAddress); + + vm.startBroadcast(orderlyPrivateKey); + + ILedger ledgerImpl = new Ledger(); + admin.upgrade(ledgerProxy, address(ledgerImpl)); + // admin.upgradeAndCall(ledgerProxy, address(ledgerImpl), abi.encodeWithSignature("initialize()")); + + vm.stopBroadcast(); + } +} diff --git a/script/ledger/UpgradeMarketManager.s.sol b/script/ledger/UpgradeMarketManager.s.sol new file mode 100644 index 0000000..08c6afd --- /dev/null +++ b/script/ledger/UpgradeMarketManager.s.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "forge-std/Script.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; +import "../../src/MarketManager.sol"; + +contract UpgradeMarketManager is Script { + function run() external { + uint256 orderlyPrivateKey = vm.envUint("ORDERLY_PRIVATE_KEY"); + address adminAddress = vm.envAddress("LEDGER_PROXY_ADMIN"); + address marketManagerAddress = vm.envAddress("MARKET_MANAGER_ADDRESS"); + + ProxyAdmin admin = ProxyAdmin(adminAddress); + ITransparentUpgradeableProxy marketManagerProxy = ITransparentUpgradeableProxy(marketManagerAddress); + + vm.startBroadcast(orderlyPrivateKey); + + IMarketManager marketManagerImpl = new MarketManager(); + admin.upgrade(marketManagerProxy, address(marketManagerImpl)); + // admin.upgradeAndCall(marketManagerProxy, address(marketManagerImpl), abi.encodeWithSignature("initialize()")); + + vm.stopBroadcast(); + } +} diff --git a/script/ledger/UpgradeOperatorManager.s.sol b/script/ledger/UpgradeOperatorManager.s.sol new file mode 100644 index 0000000..e67a134 --- /dev/null +++ b/script/ledger/UpgradeOperatorManager.s.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "forge-std/Script.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; +import "../../src/OperatorManager.sol"; + +contract UpgradeOperatorManager is Script { + function run() external { + uint256 orderlyPrivateKey = vm.envUint("ORDERLY_PRIVATE_KEY"); + address adminAddress = vm.envAddress("LEDGER_PROXY_ADMIN"); + address operatorManagerAddress = vm.envAddress("OPERATOR_MANAGER_ADDRESS"); + + ProxyAdmin admin = ProxyAdmin(adminAddress); + ITransparentUpgradeableProxy operatorManagerProxy = ITransparentUpgradeableProxy(operatorManagerAddress); + + vm.startBroadcast(orderlyPrivateKey); + + IOperatorManager operatorManagerImpl = new OperatorManager(); + admin.upgrade(operatorManagerProxy, address(operatorManagerImpl)); + // admin.upgradeAndCall( + // operatorManagerProxy, address(operatorManagerImpl), abi.encodeWithSignature("initialize()") + // ); + + vm.stopBroadcast(); + } +} diff --git a/script/ledger/UpgradeVaultManager.s.sol b/script/ledger/UpgradeVaultManager.s.sol new file mode 100644 index 0000000..cf4778c --- /dev/null +++ b/script/ledger/UpgradeVaultManager.s.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "forge-std/Script.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; +import "../../src/VaultManager.sol"; + +contract UpgradeVaultManager is Script { + function run() external { + uint256 orderlyPrivateKey = vm.envUint("ORDERLY_PRIVATE_KEY"); + address adminAddress = vm.envAddress("LEDGER_PROXY_ADMIN"); + address vaultManagerAddress = vm.envAddress("VAULT_MANAGER_ADDRESS"); + + ProxyAdmin admin = ProxyAdmin(adminAddress); + ITransparentUpgradeableProxy vaultManagerProxy = ITransparentUpgradeableProxy(vaultManagerAddress); + + vm.startBroadcast(orderlyPrivateKey); + + IVaultManager vaultManagerImpl = new VaultManager(); + admin.upgrade(vaultManagerProxy, address(vaultManagerImpl)); + // admin.upgradeAndCall(vaultManagerProxy, address(vaultManagerImpl), abi.encodeWithSignature("initialize()")); + + vm.stopBroadcast(); + } +} diff --git a/script/ledgerV2/DeployNewFeeManager.s.sol b/script/ledgerV2/DeployNewFeeManager.s.sol new file mode 100644 index 0000000..0fd05a1 --- /dev/null +++ b/script/ledgerV2/DeployNewFeeManager.s.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "forge-std/Script.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; +import "../../src/FeeManager.sol"; +import "../utils/BaseScript.s.sol"; +import "../utils/ConfigHelper.s.sol"; + +contract DeployNewFeeManager is BaseScript, ConfigHelper { + function run() external { + uint256 orderlyPrivateKey = vm.envUint("ORDERLY_PRIVATE_KEY"); + Envs memory envs = getEnvs(); + string memory env = envs.env; + string memory network = envs.ledgerNetwork; + + LedgerDeployData memory config = getLedgerDeployData(env, network); + address feeManagerAddress = config.feeManager; + console.log("feeManagerAddress: ", feeManagerAddress); + + vm.startBroadcast(orderlyPrivateKey); + IFeeManager feeManagerImpl = new FeeManager(); + console.log("new feeManagerImplAddress: ", address(feeManagerImpl)); + vm.stopBroadcast(); + console.log("deply done"); + } +} diff --git a/script/ledgerV2/DeployNewLedger.s.sol b/script/ledgerV2/DeployNewLedger.s.sol new file mode 100644 index 0000000..aeb7994 --- /dev/null +++ b/script/ledgerV2/DeployNewLedger.s.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "forge-std/Script.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; +import "../../src/Ledger.sol"; +import "../utils/BaseScript.s.sol"; +import "../utils/ConfigHelper.s.sol"; + +contract DeployNewLedger is BaseScript, ConfigHelper { + function run() external { + uint256 orderlyPrivateKey = vm.envUint("ORDERLY_PRIVATE_KEY"); + Envs memory envs = getEnvs(); + string memory env = envs.env; + string memory network = envs.ledgerNetwork; + + LedgerDeployData memory config = getLedgerDeployData(env, network); + address ledgerAddress = config.ledger; + console.log("ledgerAddress: ", ledgerAddress); + + vm.startBroadcast(orderlyPrivateKey); + ILedger ledgerImpl = new Ledger(); + console.log("new ledgerImplAddress: ", address(ledgerImpl)); + vm.stopBroadcast(); + console.log("deploy done"); + } +} diff --git a/script/ledgerV2/DeployNewLedgerImplA.s.sol b/script/ledgerV2/DeployNewLedgerImplA.s.sol new file mode 100644 index 0000000..c127782 --- /dev/null +++ b/script/ledgerV2/DeployNewLedgerImplA.s.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "forge-std/Script.sol"; +import "../../src/LedgerImplA.sol"; +import "../utils/BaseScript.s.sol"; +import "../utils/ConfigHelper.s.sol"; + +contract DeployNewLedgerImplA is BaseScript, ConfigHelper { + function run() external { + uint256 orderlyPrivateKey = vm.envUint("ORDERLY_PRIVATE_KEY"); + + vm.startBroadcast(orderlyPrivateKey); + LedgerImplA ledgerImplA = new LedgerImplA(); + console.log("new ledgerImplA Address: ", address(ledgerImplA)); + vm.stopBroadcast(); + + console.log("All done!"); + } +} diff --git a/script/ledgerV2/DeployNewMarketManager.s.sol b/script/ledgerV2/DeployNewMarketManager.s.sol new file mode 100644 index 0000000..f8954e5 --- /dev/null +++ b/script/ledgerV2/DeployNewMarketManager.s.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "forge-std/Script.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; +import "../../src/MarketManager.sol"; +import "../utils/BaseScript.s.sol"; +import "../utils/ConfigHelper.s.sol"; + +contract DeployNewMarketManager is BaseScript, ConfigHelper { + function run() external { + uint256 orderlyPrivateKey = vm.envUint("ORDERLY_PRIVATE_KEY"); + Envs memory envs = getEnvs(); + string memory env = envs.env; + string memory network = envs.ledgerNetwork; + + LedgerDeployData memory config = getLedgerDeployData(env, network); + address marketManagerAddress = config.marketManager; + console.log("marketManagerAddress: ", marketManagerAddress); + + vm.startBroadcast(orderlyPrivateKey); + IMarketManager marketManagerImpl = new MarketManager(); + console.log("new marketManagerImplAddress: ", address(marketManagerImpl)); + vm.stopBroadcast(); + console.log("deploy done"); + } +} diff --git a/script/ledgerV2/DeployNewOperatorManager.s.sol b/script/ledgerV2/DeployNewOperatorManager.s.sol new file mode 100644 index 0000000..3c8cf26 --- /dev/null +++ b/script/ledgerV2/DeployNewOperatorManager.s.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "forge-std/Script.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; +import "../../src/OperatorManager.sol"; +import "../utils/BaseScript.s.sol"; +import "../utils/ConfigHelper.s.sol"; + +contract DeployNewOperatorManager is BaseScript, ConfigHelper { + function run() external { + uint256 orderlyPrivateKey = vm.envUint("ORDERLY_PRIVATE_KEY"); + Envs memory envs = getEnvs(); + string memory env = envs.env; + string memory network = envs.ledgerNetwork; + + LedgerDeployData memory config = getLedgerDeployData(env, network); + address operatorManagerAddress = config.operatorManager; + console.log("operatorManagerAddress: ", operatorManagerAddress); + + vm.startBroadcast(orderlyPrivateKey); + IOperatorManager operatorManagerImpl = new OperatorManager(); + console.log("new operatorManagerImplAddress: ", address(operatorManagerImpl)); + vm.stopBroadcast(); + console.log("deploy done"); + } +} diff --git a/script/ledgerV2/DeployNewVaultManager.s.sol b/script/ledgerV2/DeployNewVaultManager.s.sol new file mode 100644 index 0000000..17b86f6 --- /dev/null +++ b/script/ledgerV2/DeployNewVaultManager.s.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "forge-std/Script.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; +import "../../src/VaultManager.sol"; +import "../utils/BaseScript.s.sol"; +import "../utils/ConfigHelper.s.sol"; + +contract DeployNewVaultManager is BaseScript, ConfigHelper { + function run() external { + uint256 orderlyPrivateKey = vm.envUint("ORDERLY_PRIVATE_KEY"); + Envs memory envs = getEnvs(); + string memory env = envs.env; + string memory network = envs.ledgerNetwork; + + LedgerDeployData memory config = getLedgerDeployData(env, network); + address vaultManagerAddress = config.vaultManager; + console.log("vaultManagerAddress: ", vaultManagerAddress); + + vm.startBroadcast(orderlyPrivateKey); + IVaultManager vaultManagerImpl = new VaultManager(); + console.log("new vaultManagerImplAddress: ", address(vaultManagerImpl)); + vm.stopBroadcast(); + console.log("deploy done"); + } +} diff --git a/script/ledgerV2/DeployProxyLedger.s.sol b/script/ledgerV2/DeployProxyLedger.s.sol new file mode 100644 index 0000000..1be14ef --- /dev/null +++ b/script/ledgerV2/DeployProxyLedger.s.sol @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "forge-std/Script.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; +import "../../src/OperatorManager.sol"; +import "../../src/Ledger.sol"; +import "../../src/VaultManager.sol"; +import "../../src/FeeManager.sol"; +import "../../src/MarketManager.sol"; +import "../utils/BaseScript.s.sol"; +import "../utils/ConfigHelper.s.sol"; + +contract DeployLedger is BaseScript, ConfigHelper { + string env; + string network; + + function run() external { + uint256 orderlyPrivateKey = vm.envUint("ORDERLY_PRIVATE_KEY"); + Envs memory envs = getEnvs(); + env = envs.env; + network = envs.ledgerNetwork; + + vm.startBroadcast(orderlyPrivateKey); + + ProxyAdmin admin = new ProxyAdmin(); + + // avoid stack too deep error + { + console.log("deployed proxyAdmin address: ", address(admin)); + writeLedgerDeployData(env, network, "proxyAdmin", vm.toString(address(admin))); + } + + IOperatorManager operatorManagerImpl = new OperatorManager(); + IVaultManager vaultManagerImpl = new VaultManager(); + ILedger ledgerImpl = new Ledger(); + IFeeManager feeImpl = new FeeManager(); + IMarketManager marketImpl = new MarketManager(); + + bytes memory initData = abi.encodeWithSignature("initialize()"); + + TransparentUpgradeableProxy operatorProxy = + new TransparentUpgradeableProxy(address(operatorManagerImpl), address(admin), initData); + TransparentUpgradeableProxy vaultProxy = + new TransparentUpgradeableProxy(address(vaultManagerImpl), address(admin), initData); + TransparentUpgradeableProxy ledgerProxy = + new TransparentUpgradeableProxy(address(ledgerImpl), address(admin), initData); + TransparentUpgradeableProxy feeProxy = + new TransparentUpgradeableProxy(address(feeImpl), address(admin), initData); + TransparentUpgradeableProxy marketProxy = + new TransparentUpgradeableProxy(address(marketImpl), address(admin), initData); + + // avoid stack too deep error + { + console.log("deployed operatorManager proxy address: ", address(operatorProxy)); + console.log("deployed vaultManager proxy address: ", address(vaultProxy)); + console.log("deployed ledger proxy address: ", address(ledgerProxy)); + console.log("deployed feeManager proxy address: ", address(feeProxy)); + console.log("deployed marketManager proxy address: ", address(marketProxy)); + writeLedgerDeployData(env, network, "operatorManager", vm.toString(address(operatorProxy))); + writeLedgerDeployData(env, network, "vaultManager", vm.toString(address(vaultProxy))); + writeLedgerDeployData(env, network, "ledger", vm.toString(address(ledgerProxy))); + writeLedgerDeployData(env, network, "feeManager", vm.toString(address(feeProxy))); + writeLedgerDeployData(env, network, "marketManager", vm.toString(address(marketProxy))); + + console.log("deployed success"); + } + + IOperatorManager operatorManager = IOperatorManager(address(operatorProxy)); + IVaultManager vaultManager = IVaultManager(address(vaultProxy)); + ILedger ledger = ILedger(address(ledgerProxy)); + IFeeManager feeManager = IFeeManager(address(feeProxy)); + IMarketManager marketManager = IMarketManager(address(marketProxy)); + + // avoid stack too deep error + { + LedgerDeployData memory config = getLedgerDeployData(env, network); + address operatorAdminAddress = config.operatorAddress; + console.log("operatorAdminAddress: ", operatorAdminAddress); + + ledger.setOperatorManagerAddress(address(operatorManager)); + ledger.setVaultManager(address(vaultManager)); + ledger.setFeeManager(address(feeManager)); + ledger.setMarketManager(address(marketManager)); + + operatorManager.setOperator(operatorAdminAddress); + operatorManager.setLedger(address(ledger)); + operatorManager.setMarketManager(address(marketManager)); + + vaultManager.setLedgerAddress(address(ledger)); + + feeManager.setLedgerAddress(address(ledger)); + + marketManager.setOperatorManagerAddress(address(operatorManager)); + marketManager.setLedgerAddress(address(ledger)); + } + vm.stopBroadcast(); + + console.log("All done!"); + } +} diff --git a/script/ledgerV2/FixLedger.s.sol b/script/ledgerV2/FixLedger.s.sol new file mode 100644 index 0000000..1c3d0e3 --- /dev/null +++ b/script/ledgerV2/FixLedger.s.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "forge-std/Script.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; +import "../../src/OperatorManager.sol"; +import "../../src/Ledger.sol"; +import "../../src/VaultManager.sol"; +import "../../src/FeeManager.sol"; +import "../../src/MarketManager.sol"; +import "../utils/BaseScript.s.sol"; +import "../utils/ConfigHelper.s.sol"; + +contract FixLedger is BaseScript, ConfigHelper { + function run() external { + uint256 orderlyPrivateKey = vm.envUint("ORDERLY_PRIVATE_KEY"); + Envs memory envs = getEnvs(); + string memory env = envs.env; + string memory network = envs.ledgerNetwork; + + LedgerDeployData memory config = getLedgerDeployData(env, network); + address ledgerAddress = config.ledger; + address operatorManagerAddress = config.operatorManager; + address vaultManagerAddresss = config.vaultManager; + address feeManagerAddress = config.feeManager; + address marketManagerAddress = config.marketManager; + address operatorAdminAddress = config.operatorAddress; + console.log("ledgerAddress: ", ledgerAddress); + console.log("operatorManagerAddress: ", operatorManagerAddress); + console.log("vaultManagerAddresss: ", vaultManagerAddresss); + console.log("feeManagerAddress: ", feeManagerAddress); + console.log("marketManagerAddress: ", marketManagerAddress); + console.log("operatorAdminAddress: ", operatorAdminAddress); + + vm.startBroadcast(orderlyPrivateKey); + + IOperatorManager operatorManager = IOperatorManager(address(operatorManagerAddress)); + ILedger ledger = ILedger(address(ledgerAddress)); + IVaultManager vaultManager = IVaultManager(address(vaultManagerAddresss)); + IFeeManager feeManager = IFeeManager(address(feeManagerAddress)); + IMarketManager marketManager = IMarketManager(address(marketManagerAddress)); + + // avoid stack too deep error + { + ledger.setOperatorManagerAddress(address(operatorManager)); + ledger.setVaultManager(address(vaultManager)); + ledger.setFeeManager(address(feeManager)); + ledger.setMarketManager(address(marketManager)); + + operatorManager.setOperator(operatorAdminAddress); + operatorManager.setLedger(address(ledger)); + operatorManager.setMarketManager(address(marketManager)); + + vaultManager.setLedgerAddress(address(ledger)); + + feeManager.setLedgerAddress(address(ledger)); + + marketManager.setOperatorManagerAddress(address(operatorManager)); + marketManager.setLedgerAddress(address(ledger)); + } + vm.stopBroadcast(); + + console.log("All done!"); + } +} diff --git a/script/ledgerV2/SetCrossChainManager.s.sol b/script/ledgerV2/SetCrossChainManager.s.sol new file mode 100644 index 0000000..7bbcf8c --- /dev/null +++ b/script/ledgerV2/SetCrossChainManager.s.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "forge-std/Script.sol"; +import "../../src/Ledger.sol"; +import "../utils/BaseScript.s.sol"; +import "../utils/ConfigHelper.s.sol"; + +contract SetCrossChainManager is BaseScript, ConfigHelper { + function run() external { + uint256 orderlyPrivateKey = vm.envUint("ORDERLY_PRIVATE_KEY"); + address ledgerCrossChainManagerAddress = vm.envAddress("LEDGER_CROSS_CHAIN_MANAGER_ADDRESS"); // FIXME + Envs memory envs = getEnvs(); + string memory env = envs.env; + string memory network = envs.ledgerNetwork; + + LedgerDeployData memory config = getLedgerDeployData(env, network); + address adminAddress = config.proxyAdmin; + address ledgerAddress = config.ledger; + console.log("adminAddress: ", adminAddress); + console.log("ledgerAddress: ", ledgerAddress); + + vm.startBroadcast(orderlyPrivateKey); + + ILedger ledger = Ledger(ledgerAddress); + ledger.setCrossChainManager(ledgerCrossChainManagerAddress); + + vm.stopBroadcast(); + console.log("setCrossChainManager done"); + } +} diff --git a/script/ledgerV2/TransferOwner.s.sol b/script/ledgerV2/TransferOwner.s.sol new file mode 100644 index 0000000..aee68e3 --- /dev/null +++ b/script/ledgerV2/TransferOwner.s.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "forge-std/Script.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; +import "../../src/OperatorManager.sol"; +import "../../src/Ledger.sol"; +import "../../src/VaultManager.sol"; +import "../../src/FeeManager.sol"; +import "../../src/MarketManager.sol"; +import "../utils/BaseScript.s.sol"; +import "../utils/ConfigHelper.s.sol"; + +contract TransferOwner is BaseScript, ConfigHelper { + function run() external { + uint256 orderlyPrivateKey = vm.envUint("ORDERLY_PRIVATE_KEY"); + Envs memory envs = getEnvs(); + string memory env = envs.env; + string memory network = envs.ledgerNetwork; + + LedgerDeployData memory config = getLedgerDeployData(env, network); + address adminAddress = config.proxyAdmin; + address feeManagerAddress = config.feeManager; + address marketManagerAddress = config.marketManager; + address operatorManagerAddress = config.operatorManager; + address vaultManagerAddress = config.vaultManager; + address ledgerAddress = config.ledger; + address multiSigAddress = config.multiSig; + console.log("adminAddress: ", adminAddress); + console.log("feeManagerAddress: ", feeManagerAddress); + console.log("marketManagerAddress: ", marketManagerAddress); + console.log("operatorManagerAddress: ", operatorManagerAddress); + console.log("vaultManagerAddress: ", vaultManagerAddress); + console.log("ledgerAddress: ", ledgerAddress); + console.log("multiSigAddress: ", multiSigAddress); + + vm.startBroadcast(orderlyPrivateKey); + + { + // first change the owner of the impls + OperatorManager operatorManager = OperatorManager(operatorManagerAddress); + VaultManager vaultManager = VaultManager(vaultManagerAddress); + Ledger ledger = Ledger(ledgerAddress); + FeeManager feeManager = FeeManager(feeManagerAddress); + MarketManager marketManager = MarketManager(marketManagerAddress); + + operatorManager.transferOwnership(multiSigAddress); + vaultManager.transferOwnership(multiSigAddress); + ledger.transferOwnership(multiSigAddress); + feeManager.transferOwnership(multiSigAddress); + marketManager.transferOwnership(multiSigAddress); + } + + { + // second change the owner of the ProxyAdmin + ProxyAdmin admin = ProxyAdmin(adminAddress); + admin.transferOwnership(multiSigAddress); + } + + vm.stopBroadcast(); + console.log("transfer owner done"); + } +} diff --git a/script/ledgerV2/UpdateSymbol.s.sol b/script/ledgerV2/UpdateSymbol.s.sol new file mode 100644 index 0000000..e7f4233 --- /dev/null +++ b/script/ledgerV2/UpdateSymbol.s.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "forge-std/Script.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; +import "../../src/VaultManager.sol"; +import "../utils/BaseScript.s.sol"; +import "../utils/ConfigHelper.s.sol"; + +contract UpdateSymbol is BaseScript, ConfigHelper { + function run() external { + uint256 orderlyPrivateKey = vm.envUint("ORDERLY_PRIVATE_KEY"); + + Envs memory envs = getEnvs(); + string memory env = envs.env; + string memory network = envs.ledgerNetwork; + console.log("env: ", env); + console.log("network: ", network); + + LedgerDeployData memory config = getLedgerDeployData(env, network); + address adminAddress = config.proxyAdmin; + address vaultManagerAddress = config.vaultManager; + console.log("adminAddress: ", adminAddress); + console.log("vaultManagerAddress: ", vaultManagerAddress); + + vm.startBroadcast(orderlyPrivateKey); + + IVaultManager vaultManager = IVaultManager(vaultManagerAddress); + vaultManager.setAllowedSymbol(0x5a8133e52befca724670dbf2cade550c522c2410dd5b1189df675e99388f509d, true); // PERP_BTC_USDC + vaultManager.setAllowedSymbol(0x5d0471b083610a6f3b572fc8b0f759c5628e74159816681fb7d927b9263de60b, true); // PERP_WOO_USDC + + vm.stopBroadcast(); + console.log("update done"); + } +} diff --git a/script/ledgerV2/UpgradeFeeManager.s.sol b/script/ledgerV2/UpgradeFeeManager.s.sol new file mode 100644 index 0000000..0ed7c6b --- /dev/null +++ b/script/ledgerV2/UpgradeFeeManager.s.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "forge-std/Script.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; +import "../../src/FeeManager.sol"; +import "../utils/BaseScript.s.sol"; +import "../utils/ConfigHelper.s.sol"; + +contract UpgradeFeeManager is BaseScript, ConfigHelper { + function run() external { + uint256 orderlyPrivateKey = vm.envUint("ORDERLY_PRIVATE_KEY"); + Envs memory envs = getEnvs(); + string memory env = envs.env; + string memory network = envs.ledgerNetwork; + + LedgerDeployData memory config = getLedgerDeployData(env, network); + address adminAddress = config.proxyAdmin; + address feeManagerAddress = config.feeManager; + console.log("adminAddress: ", adminAddress); + console.log("feeManagerAddress: ", feeManagerAddress); + + ProxyAdmin admin = ProxyAdmin(adminAddress); + ITransparentUpgradeableProxy feeManagerProxy = ITransparentUpgradeableProxy(feeManagerAddress); + + vm.startBroadcast(orderlyPrivateKey); + + IFeeManager feeManagerImpl = new FeeManager(); + admin.upgrade(feeManagerProxy, address(feeManagerImpl)); + + vm.stopBroadcast(); + console.log("upgrade done"); + } +} diff --git a/script/ledgerV2/UpgradeLedger.s.sol b/script/ledgerV2/UpgradeLedger.s.sol new file mode 100644 index 0000000..e6eb29a --- /dev/null +++ b/script/ledgerV2/UpgradeLedger.s.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "forge-std/Script.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; +import "../../src/Ledger.sol"; +import "../utils/BaseScript.s.sol"; +import "../utils/ConfigHelper.s.sol"; + +contract UpgradeLedger is BaseScript, ConfigHelper { + function run() external { + uint256 orderlyPrivateKey = vm.envUint("ORDERLY_PRIVATE_KEY"); + Envs memory envs = getEnvs(); + string memory env = envs.env; + string memory network = envs.ledgerNetwork; + + LedgerDeployData memory config = getLedgerDeployData(env, network); + address adminAddress = config.proxyAdmin; + address ledgerAddress = config.ledger; + console.log("adminAddress: ", adminAddress); + console.log("ledgerAddress: ", ledgerAddress); + + ProxyAdmin admin = ProxyAdmin(adminAddress); + ITransparentUpgradeableProxy ledgerProxy = ITransparentUpgradeableProxy(ledgerAddress); + + vm.startBroadcast(orderlyPrivateKey); + + ILedger ledgerImpl = new Ledger(); + admin.upgrade(ledgerProxy, address(ledgerImpl)); + // admin.upgradeAndCall(ledgerProxy, address(ledgerImpl), abi.encodeWithSignature("upgradeCall()")); + + vm.stopBroadcast(); + console.log("upgrade done"); + } +} diff --git a/script/ledgerV2/UpgradeMarketManager.s.sol b/script/ledgerV2/UpgradeMarketManager.s.sol new file mode 100644 index 0000000..6703573 --- /dev/null +++ b/script/ledgerV2/UpgradeMarketManager.s.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "forge-std/Script.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; +import "../../src/MarketManager.sol"; +import "../utils/BaseScript.s.sol"; +import "../utils/ConfigHelper.s.sol"; + +contract UpgradeMarketManager is BaseScript, ConfigHelper { + function run() external { + uint256 orderlyPrivateKey = vm.envUint("ORDERLY_PRIVATE_KEY"); + Envs memory envs = getEnvs(); + string memory env = envs.env; + string memory network = envs.ledgerNetwork; + + LedgerDeployData memory config = getLedgerDeployData(env, network); + address adminAddress = config.proxyAdmin; + address marketManagerAddress = config.marketManager; + console.log("adminAddress: ", adminAddress); + console.log("marketManagerAddress: ", marketManagerAddress); + + ProxyAdmin admin = ProxyAdmin(adminAddress); + ITransparentUpgradeableProxy marketManagerProxy = ITransparentUpgradeableProxy(marketManagerAddress); + + vm.startBroadcast(orderlyPrivateKey); + + IMarketManager marketManagerImpl = new MarketManager(); + admin.upgrade(marketManagerProxy, address(marketManagerImpl)); + + vm.stopBroadcast(); + console.log("upgrade done"); + } +} diff --git a/script/ledgerV2/UpgradeOperatorManager.s.sol b/script/ledgerV2/UpgradeOperatorManager.s.sol new file mode 100644 index 0000000..49ce5ca --- /dev/null +++ b/script/ledgerV2/UpgradeOperatorManager.s.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "forge-std/Script.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; +import "../../src/OperatorManager.sol"; +import "../utils/BaseScript.s.sol"; +import "../utils/ConfigHelper.s.sol"; + +contract UpgradeOperatorManager is BaseScript, ConfigHelper { + function run() external { + uint256 orderlyPrivateKey = vm.envUint("ORDERLY_PRIVATE_KEY"); + Envs memory envs = getEnvs(); + string memory env = envs.env; + string memory network = envs.ledgerNetwork; + + LedgerDeployData memory config = getLedgerDeployData(env, network); + address adminAddress = config.proxyAdmin; + address operatorManagerAddress = config.operatorManager; + console.log("adminAddress: ", adminAddress); + console.log("operatorManagerAddress: ", operatorManagerAddress); + + ProxyAdmin admin = ProxyAdmin(adminAddress); + ITransparentUpgradeableProxy operatorManagerProxy = ITransparentUpgradeableProxy(operatorManagerAddress); + + vm.startBroadcast(orderlyPrivateKey); + + IOperatorManager operatorManagerImpl = new OperatorManager(); + admin.upgrade(operatorManagerProxy, address(operatorManagerImpl)); + + vm.stopBroadcast(); + console.log("upgrade done"); + } +} diff --git a/script/ledgerV2/UpgradeVaultManager.s.sol b/script/ledgerV2/UpgradeVaultManager.s.sol new file mode 100644 index 0000000..003724f --- /dev/null +++ b/script/ledgerV2/UpgradeVaultManager.s.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "forge-std/Script.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; +import "../../src/VaultManager.sol"; +import "../utils/BaseScript.s.sol"; +import "../utils/ConfigHelper.s.sol"; + +contract UpgradeVaultManager is BaseScript, ConfigHelper { + function run() external { + uint256 orderlyPrivateKey = vm.envUint("ORDERLY_PRIVATE_KEY"); + Envs memory envs = getEnvs(); + string memory env = envs.env; + string memory network = envs.ledgerNetwork; + + LedgerDeployData memory config = getLedgerDeployData(env, network); + address adminAddress = config.proxyAdmin; + address vaultManagerAddress = config.vaultManager; + console.log("adminAddress: ", adminAddress); + console.log("vaultManagerAddress: ", vaultManagerAddress); + + ProxyAdmin admin = ProxyAdmin(adminAddress); + ITransparentUpgradeableProxy vaultManagerProxy = ITransparentUpgradeableProxy(vaultManagerAddress); + + vm.startBroadcast(orderlyPrivateKey); + + IVaultManager vaultManagerImpl = new VaultManager(); + admin.upgrade(vaultManagerProxy, address(vaultManagerImpl)); + + vm.stopBroadcast(); + console.log("upgrade done"); + } +} diff --git a/script/utils/BaseScript.s.sol b/script/utils/BaseScript.s.sol new file mode 100644 index 0000000..380cd09 --- /dev/null +++ b/script/utils/BaseScript.s.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +import "forge-std/Script.sol"; +import "./Utils.sol"; + +contract BaseScript is Script { + using StringUtils for string; + + function vmSelectRpcAndBroadcast(string memory network) internal { + string memory rpcUrl = getRpcUrl(network); + uint256 pk = getPrivateKey(network); + vm.createSelectFork(rpcUrl); + vm.startBroadcast(pk); + } + + function getRpcUrl(string memory network) internal view returns (string memory) { + return vm.envString(string("RPC_URL_").concat(network.toUpperCase())); + } + + function getPrivateKey(string memory network) internal view returns (uint256) { + return vm.envUint(network.toUpperCase().concat("_PRIVATE_KEY")); + } + + function getLzEndpoint(string memory network) internal view returns (address) { + return vm.envAddress(network.toUpperCase().concat("_ENDPOINT")); + } + + function getRelayProxyAddress(string memory network) internal view returns (address) { + return vm.envAddress(network.toUpperCase().concat("_RELAY_PROXY")); + } + + function getManagerProxyAddress(string memory network) internal view returns (address) { + return vm.envAddress(network.toUpperCase().concat("_MANAGER_PROXY")); + } + + function getChainId(string memory network) internal view returns (uint256) { + return vm.envUint(network.toUpperCase().concat("_CHAIN_ID")); + } + + function getLzChainId(string memory network) internal view returns (uint16) { + return uint16(vm.envUint(network.toUpperCase().concat("_LZ_CHAIN_ID"))); +} + + function getOperatorManagerAddress(string memory network) internal view returns (address) { + return vm.envAddress(network.toUpperCase().concat("_OPERATOR_MANAGER")); + } + + function getVaultAddress(string memory network) internal view returns (address) { + return vm.envAddress(network.toUpperCase().concat("_VAULT")); + } + + function getLedgerAddress(string memory network) internal view returns (address) { + return vm.envAddress(network.toUpperCase().concat("_LEDGER")); + } +} diff --git a/script/utils/ConfigHelper.s.sol b/script/utils/ConfigHelper.s.sol new file mode 100644 index 0000000..2e7885b --- /dev/null +++ b/script/utils/ConfigHelper.s.sol @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +import "forge-std/Script.sol"; +import "./Utils.sol"; + +// field name must be in alphabetical order and the same as json key +struct LedgerDeployData { + address feeManager; + address ledger; + address marketManager; + address multiSig; + address operatorAddress; + address operatorManager; + address proxyAdmin; + address vaultManager; +} + +struct ZipDeployData { + address zip; +} + +struct VaultDeployData { + address multiSig; + address proxyAdmin; + address usdc; + address vault; +} + +struct Envs { + string env; + string ledgerNetwork; + string vaultNetwork; +} + +contract ConfigHelper is Script { + using StringUtils for string; + + string constant ENVS_FILE = "./config/tasks/ledger-vault-envs.json"; + string constant DEPLOY_LEDGER_SAVE_FILE = "./config/deploy-ledger.json"; + string constant DEPLOY_VAULT_SAVE_FILE = "./config/deploy-vault.json"; + string constant DEPLOY_ZIP_SAVE_FILE = "./config/deploy-zip.json"; + + function getConfigFileData(string memory envVar) internal returns (bytes memory) { + string memory configFile = vm.envString(envVar); + string memory fileData = vm.readFile(configFile); + bytes memory encodedData = vm.parseJson(fileData); + + vm.closeFile(configFile); + + return encodedData; + } + + function formKey(string memory key1, string memory key2) internal pure returns (string memory) { + return key1.formJsonKey().concat(key2.formJsonKey()); + } + + function formKey(string memory key1, string memory key2, string memory key3) + internal + pure + returns (string memory) + { + return key1.formJsonKey().concat(key2.formJsonKey().concat(key3.formJsonKey())); + } + + function getValueByKey(string memory path, string memory key1, string memory key2, string memory key3) + internal + view + returns (bytes memory) + { + string memory fileData = vm.readFile(path); + bytes memory encodedData = vm.parseJson(fileData, formKey(key1, key2, key3)); + return encodedData; + } + + function getEnvs() internal returns (Envs memory) { + string memory fileData = vm.readFile(ENVS_FILE); + vm.closeFile(ENVS_FILE); + bytes memory encodedData = vm.parseJson(fileData); + Envs memory envs = abi.decode(encodedData, (Envs)); + return envs; + } + + function getLedgerDeployData(string memory env, string memory network) internal returns (LedgerDeployData memory) { + string memory deployData = vm.readFile(DEPLOY_LEDGER_SAVE_FILE); + string memory networkKey = env.formJsonKey().concat(network.formJsonKey()); + bytes memory networkEncodeData = vm.parseJson(deployData, networkKey); + LedgerDeployData memory networkRelayData = abi.decode(networkEncodeData, (LedgerDeployData)); + // close file + vm.closeFile(DEPLOY_LEDGER_SAVE_FILE); + return networkRelayData; + } + + function getZipDeployData(string memory env, string memory network) internal returns (ZipDeployData memory) { + string memory deployData = vm.readFile(DEPLOY_ZIP_SAVE_FILE); + string memory networkKey = env.formJsonKey().concat(network.formJsonKey()); + bytes memory networkEncodeData = vm.parseJson(deployData, networkKey); + ZipDeployData memory networkRelayData = abi.decode(networkEncodeData, (ZipDeployData)); + // close file + vm.closeFile(DEPLOY_ZIP_SAVE_FILE); + return networkRelayData; + } + + function getVaultDeployData(string memory env, string memory network) internal returns (VaultDeployData memory) { + string memory deployData = vm.readFile(DEPLOY_VAULT_SAVE_FILE); + string memory networkKey = env.formJsonKey().concat(network.formJsonKey()); + bytes memory networkEncodeData = vm.parseJson(deployData, networkKey); + VaultDeployData memory networkRelayData = abi.decode(networkEncodeData, (VaultDeployData)); + // close file + vm.closeFile(DEPLOY_VAULT_SAVE_FILE); + return networkRelayData; + } + + function writeLedgerDeployData(string memory env, string memory network, string memory key, string memory value) + internal + { + writeDeployData(env, network, key, value, DEPLOY_LEDGER_SAVE_FILE); + } + + function writeVaultDeployData(string memory env, string memory network, string memory key, string memory value) + internal + { + writeDeployData(env, network, key, value, DEPLOY_VAULT_SAVE_FILE); + } + + function writeZipDeployData(string memory env, string memory network, string memory key, string memory value) + internal + { + writeDeployData(env, network, key, value, DEPLOY_ZIP_SAVE_FILE); + } + + function writeDeployData( + string memory env, + string memory network, + string memory key, + string memory value, + string memory deploySavePath + ) internal { + string memory networkKey = env.formJsonKey().concat(network.formJsonKey()).concat(key.formJsonKey()); + vm.writeJson(value, deploySavePath, networkKey); + } + + function writeToJsonFileByKey(string memory value, string memory path, string memory key1, string memory key2) + internal + { + vm.writeJson(value, path, formKey(key1, key2)); + } + + function writeToJsonFileByKey( + string memory value, + string memory path, + string memory key1, + string memory key2, + string memory key3 + ) internal { + vm.writeJson(value, path, formKey(key1, key2, key3)); + } +} diff --git a/script/utils/Utils.sol b/script/utils/Utils.sol new file mode 100644 index 0000000..9e71ba8 --- /dev/null +++ b/script/utils/Utils.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +library StringUtils { + function compare(string memory a, string memory b) internal pure returns (bool) { + return (keccak256(abi.encodePacked((a))) == keccak256(abi.encodePacked((b)))); + } + + function toUpperCase(string memory a) internal pure returns (string memory) { + // clone a, do not modify input + bytes memory b = bytes(a); + bytes memory c = new bytes(b.length); + for (uint256 i = 0; i < b.length; i++) { + if ((b[i] >= 0x61) && (b[i] <= 0x7A)) { + c[i] = bytes1(uint8(b[i]) - 0x20); + } else { + c[i] = b[i]; + } + } + return string(c); + } + + function toLowerCase(string memory a) internal pure returns (string memory) { + // clone a, do not modify input + bytes memory b = bytes(a); + bytes memory c = new bytes(b.length); + for (uint256 i = 0; i < b.length; i++) { + if ((b[i] >= 0x41) && (b[i] <= 0x5A)) { + c[i] = bytes1(uint8(b[i]) + 0x20); + } else { + c[i] = b[i]; + } + } + return string(c); + } + + function concat(string memory a, string memory b) internal pure returns (string memory) { + return string(abi.encodePacked(a, b)); + } + + function formJsonKey(string memory a) internal pure returns (string memory) { + return string(abi.encodePacked(".", a)); + } +} diff --git a/script/vault/DeployProxyVault.s.sol b/script/vault/DeployProxyVault.s.sol new file mode 100644 index 0000000..f35fd02 --- /dev/null +++ b/script/vault/DeployProxyVault.s.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "forge-std/Script.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; +import "../../src/vaultSide/Vault.sol"; +import "../../src/vaultSide/tUSDC.sol"; + +contract DeployVault is Script { + bytes32 constant USDC = 0xd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa; + bytes32 constant BROKER_HASH = 0x083098c593f395bea1de45dda552d9f14e8fcb0be3faaa7a1903c5477d7ba7fd; + + function run() external { + uint256 orderlyPrivateKey = vm.envUint("ORDERLY_PRIVATE_KEY"); + address vaultCrossChainManagerAddress = vm.envAddress("VAULT_CROSS_CHAIN_MANAGER_ADDRESS"); + IVaultCrossChainManager vaultCrossChainManager = IVaultCrossChainManager(payable(vaultCrossChainManagerAddress)); + + vm.startBroadcast(orderlyPrivateKey); + + ProxyAdmin admin = new ProxyAdmin(); + TestUSDC tUSDC = new TestUSDC(); + + IVault vaultImpl = new Vault(); + TransparentUpgradeableProxy vaultProxy = + new TransparentUpgradeableProxy(address(vaultImpl), address(admin), abi.encodeWithSignature("initialize()")); + IVault vault = IVault(address(vaultProxy)); + + vault.changeTokenAddressAndAllow(USDC, address(tUSDC)); + vault.setAllowedBroker(BROKER_HASH, true); + vault.setCrossChainManager(address(vaultCrossChainManager)); + + // vaultCrossChainManager.setVault(address(vault)); + + vm.stopBroadcast(); + } +} diff --git a/script/vault/StartDeposit.s.sol b/script/vault/StartDeposit.s.sol new file mode 100644 index 0000000..96374bf --- /dev/null +++ b/script/vault/StartDeposit.s.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "forge-std/Script.sol"; +import "../../src/vaultSide/Vault.sol"; +import "../../src/vaultSide/tUSDC.sol"; + +contract StartDeposit is Script { + address constant userAddress = 0x4FDDB51ADe1fa66952de254bE7E1a84EEB153331; + bytes32 constant userAccountId = 0x89bf2019fe60f13ec6c3f8de8c10156c2691ba5e743260dbcd81c2c66e87cba0; + + bytes32 constant USDC = 0xd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa; + bytes32 constant BROKER_HASH = 0x083098c593f395bea1de45dda552d9f14e8fcb0be3faaa7a1903c5477d7ba7fd; + + function run() external { + uint256 userPrivateKey = vm.envUint("USER_PRIVATE_KEY"); + address vaultAddress = vm.envAddress("VAULT_ADDRESS"); + address tUSDCAddress = vm.envAddress("TEST_USDC_ADDRESS"); + vm.startBroadcast(userPrivateKey); + + IVault vault = IVault(payable(vaultAddress)); + TestUSDC tUSDC = TestUSDC(payable(tUSDCAddress)); + tUSDC.mint(userAddress, 1000 * 1e6); + tUSDC.approve(vaultAddress, 1000 * 1e6); + vault.deposit( + VaultTypes.VaultDepositFE({ + accountId: userAccountId, + tokenHash: USDC, + brokerHash: BROKER_HASH, + tokenAmount: 1000 * 1e6 + }) + ); + + vm.stopBroadcast(); + } +} diff --git a/script/vault/UpgradeVault.s.sol b/script/vault/UpgradeVault.s.sol new file mode 100644 index 0000000..528a2a3 --- /dev/null +++ b/script/vault/UpgradeVault.s.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "forge-std/Script.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; +import "../../src/vaultSide/Vault.sol"; + +contract UpgradeVault is Script { + function run() external { + uint256 orderlyPrivateKey = vm.envUint("ORDERLY_PRIVATE_KEY"); + address adminAddress = vm.envAddress("VAULT_PROXY_ADMIN"); + address vaultAddress = vm.envAddress("VAULT_ADDRESS"); + + ProxyAdmin admin = ProxyAdmin(adminAddress); + ITransparentUpgradeableProxy vaultProxy = ITransparentUpgradeableProxy(vaultAddress); + + vm.startBroadcast(orderlyPrivateKey); + + IVault vaultImpl = new Vault(); + admin.upgrade(vaultProxy, address(vaultImpl)); + // admin.upgradeAndCall(vaultProxy, address(vaultImpl), abi.encodeWithSignature("initialize()")); + + vm.stopBroadcast(); + } +} diff --git a/script/vaultV2/DeployNewVault.s.sol b/script/vaultV2/DeployNewVault.s.sol new file mode 100644 index 0000000..f873fb2 --- /dev/null +++ b/script/vaultV2/DeployNewVault.s.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "forge-std/Script.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; +import "../../src/vaultSide/Vault.sol"; +import "../utils/BaseScript.s.sol"; +import "../utils/ConfigHelper.s.sol"; + +contract DeployNewVault is BaseScript, ConfigHelper { + function run() external { + uint256 orderlyPrivateKey = vm.envUint("ORDERLY_PRIVATE_KEY"); + Envs memory envs = getEnvs(); + string memory env = envs.env; + string memory network = envs.vaultNetwork; + + VaultDeployData memory config = getVaultDeployData(env, network); + address vaultAddress = config.vault; + console.log("vaultAddress: ", vaultAddress); + + vm.startBroadcast(orderlyPrivateKey); + IVault vaultImpl = new Vault(); + console.log("new vaultImplAddress: ", address(vaultImpl)); + vm.stopBroadcast(); + console.log("deploy done"); + } +} diff --git a/script/vaultV2/DeployProxyVault.s.sol b/script/vaultV2/DeployProxyVault.s.sol new file mode 100644 index 0000000..e133f10 --- /dev/null +++ b/script/vaultV2/DeployProxyVault.s.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "forge-std/Script.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; +import "../../src/vaultSide/Vault.sol"; +import "../utils/BaseScript.s.sol"; +import "../utils/ConfigHelper.s.sol"; + +contract DeployVault is BaseScript, ConfigHelper { + bytes32 constant USDC = 0xd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa; + bytes32 constant BROKER_HASH = 0x6ca2f644ef7bd6d75953318c7f2580014941e753b3c6d54da56b3bf75dd14dfc; // woofi_pro + bytes32 constant BROKER_HASH2 = 0xd6c66cad06fe14fdb6ce9297d80d32f24d7428996d0045cbf90cc345c677ba16; // root + bytes32 constant BROKER_HASH3 = 0x95d85ced8adb371760e4b6437896a075632fbd6cefe699f8125a8bc1d9b19e5b; // orderly + + function run() external { + uint256 orderlyPrivateKey = vm.envUint("ORDERLY_PRIVATE_KEY"); + Envs memory envs = getEnvs(); + string memory env = envs.env; + string memory network = envs.vaultNetwork; + + VaultDeployData memory config = getVaultDeployData(env, network); + address usdcAddress = config.usdc; + console.log("usdcAddress: ", usdcAddress); + + vm.startBroadcast(orderlyPrivateKey); + + ProxyAdmin admin = new ProxyAdmin(); + + IVault vaultImpl = new Vault(); + TransparentUpgradeableProxy vaultProxy = + new TransparentUpgradeableProxy(address(vaultImpl), address(admin), abi.encodeWithSignature("initialize()")); + IVault vault = IVault(address(vaultProxy)); + + // avoid stack too deep error + { + console.log("deployed proxyAdmin address: ", address(admin)); + console.log("deployed vault proxy address: ", address(vaultProxy)); + writeVaultDeployData(env, network, "proxyAdmin", vm.toString(address(admin))); + writeVaultDeployData(env, network, "vault", vm.toString(address(vaultProxy))); + } + + vault.changeTokenAddressAndAllow(USDC, usdcAddress); + vault.setAllowedBroker(BROKER_HASH, true); + vault.setAllowedBroker(BROKER_HASH2, true); + vault.setAllowedBroker(BROKER_HASH3, true); + + vm.stopBroadcast(); + console.log("All done!"); + } +} diff --git a/script/vaultV2/SetCrossChainManager.s.sol b/script/vaultV2/SetCrossChainManager.s.sol new file mode 100644 index 0000000..16c81b2 --- /dev/null +++ b/script/vaultV2/SetCrossChainManager.s.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "forge-std/Script.sol"; +import "../../src/vaultSide/Vault.sol"; +import "../utils/BaseScript.s.sol"; +import "../utils/ConfigHelper.s.sol"; + +contract VaultSetCrossChainManager is BaseScript, ConfigHelper { + function run() external { + uint256 orderlyPrivateKey = vm.envUint("ORDERLY_PRIVATE_KEY"); + address vaultCrossChainManagerAddress = vm.envAddress("VAULT_CROSS_CHAIN_MANAGER_ADDRESS"); // FIXME + Envs memory envs = getEnvs(); + string memory env = envs.env; + string memory network = envs.vaultNetwork; + + VaultDeployData memory config = getVaultDeployData(env, network); + address adminAddress = config.proxyAdmin; + address vaultAddress = config.vault; + console.log("adminAddress: ", adminAddress); + console.log("vaultAddress: ", vaultAddress); + + vm.startBroadcast(orderlyPrivateKey); + + IVault vault = Vault(vaultAddress); + vault.setCrossChainManager(vaultCrossChainManagerAddress); + + vm.stopBroadcast(); + console.log("setCrossChainManager done"); + } +} diff --git a/script/vaultV2/TransferOwner.s.sol b/script/vaultV2/TransferOwner.s.sol new file mode 100644 index 0000000..57c0c3d --- /dev/null +++ b/script/vaultV2/TransferOwner.s.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "forge-std/Script.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; +import "../../src/vaultSide/Vault.sol"; +import "../utils/BaseScript.s.sol"; +import "../utils/ConfigHelper.s.sol"; + +contract TransferOwner is BaseScript, ConfigHelper { + function run() external { + uint256 orderlyPrivateKey = vm.envUint("ORDERLY_PRIVATE_KEY"); + Envs memory envs = getEnvs(); + string memory env = envs.env; + string memory network = envs.vaultNetwork; + + VaultDeployData memory config = getVaultDeployData(env, network); + address adminAddress = config.proxyAdmin; + address vaultAddress = config.vault; + address multiSigAddress = config.multiSig; + console.log("adminAddress: ", adminAddress); + console.log("vaultAddress: ", vaultAddress); + console.log("multiSigAddress: ", multiSigAddress); + + vm.startBroadcast(orderlyPrivateKey); + + { + // first change the owner of the impls + Vault vault = Vault(vaultAddress); + vault.transferOwnership(multiSigAddress); + } + + { + // second change the owner of the ProxyAdmin + ProxyAdmin admin = ProxyAdmin(adminAddress); + admin.transferOwnership(multiSigAddress); + } + + vm.stopBroadcast(); + console.log("transfer owner done"); + } +} diff --git a/script/vaultV2/UpgradeVault.s.sol b/script/vaultV2/UpgradeVault.s.sol new file mode 100644 index 0000000..8bc874f --- /dev/null +++ b/script/vaultV2/UpgradeVault.s.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "forge-std/Script.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; +import "../../src/vaultSide/Vault.sol"; +import "../utils/BaseScript.s.sol"; +import "../utils/ConfigHelper.s.sol"; + +contract UpgradeVault is BaseScript, ConfigHelper { + function run() external { + uint256 orderlyPrivateKey = vm.envUint("ORDERLY_PRIVATE_KEY"); + Envs memory envs = getEnvs(); + string memory env = envs.env; + string memory network = envs.vaultNetwork; + + VaultDeployData memory config = getVaultDeployData(env, network); + address adminAddress = config.proxyAdmin; + address vaultAddress = config.vault; + console.log("adminAddress: ", adminAddress); + console.log("vaultAddress: ", vaultAddress); + + ProxyAdmin admin = ProxyAdmin(adminAddress); + ITransparentUpgradeableProxy vaultProxy = ITransparentUpgradeableProxy(vaultAddress); + + vm.startBroadcast(orderlyPrivateKey); + + IVault vaultImpl = new Vault(); + admin.upgrade(vaultProxy, address(vaultImpl)); + + vm.stopBroadcast(); + console.log("upgrade done"); + } +} diff --git a/script/view/GetLedgerAllowedList.s.sol b/script/view/GetLedgerAllowedList.s.sol new file mode 100644 index 0000000..ce16dd5 --- /dev/null +++ b/script/view/GetLedgerAllowedList.s.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "forge-std/Script.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; +import "../../src/VaultManager.sol"; +import "../utils/BaseScript.s.sol"; +import "../utils/ConfigHelper.s.sol"; + +contract GetLedgerAllowedList is BaseScript, ConfigHelper { + function run() external { + Envs memory envs = getEnvs(); + string memory env = envs.env; + string memory network = envs.ledgerNetwork; + console.log("env: ", env); + console.log("network: ", network); + + LedgerDeployData memory config = getLedgerDeployData(env, network); + address vaultManagerAddress = config.vaultManager; + console.log("vaultManagerAddress: ", vaultManagerAddress); + IVaultManager vaultManager = IVaultManager(vaultManagerAddress); + // brokerId + bytes32[] memory allAllowedBroker = vaultManager.getAllAllowedBroker(); + console2.log("allAllowedBroker length: ", allAllowedBroker.length); + for (uint256 i = 0; i < allAllowedBroker.length; i++) { + console2.logBytes32(allAllowedBroker[i]); + } + // token + bytes32[] memory allAllowedToken = vaultManager.getAllAllowedToken(); + console2.log("allAllowedToken length: ", allAllowedToken.length); + for (uint256 i = 0; i < allAllowedToken.length; i++) { + console2.logBytes32(allAllowedToken[i]); + } + // symbol + bytes32[] memory allAllowedSymbol = vaultManager.getAllAllowedSymbol(); + console2.log("allAllowedSymbol length: ", allAllowedSymbol.length); + for (uint256 i = 0; i < allAllowedSymbol.length; i++) { + console2.logBytes32(allAllowedSymbol[i]); + } + console.log("get allowed list done"); + } +} diff --git a/script/view/GetVaultAllowedList.s.sol b/script/view/GetVaultAllowedList.s.sol new file mode 100644 index 0000000..fe98579 --- /dev/null +++ b/script/view/GetVaultAllowedList.s.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "forge-std/Script.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; +import "../../src/vaultSide/Vault.sol"; +import "../utils/BaseScript.s.sol"; +import "../utils/ConfigHelper.s.sol"; + +contract GetVaultAllowedList is BaseScript, ConfigHelper { + function run() external { + Envs memory envs = getEnvs(); + string memory env = envs.env; + string memory network = envs.vaultNetwork; + console.log("env: ", env); + console.log("network: ", network); + + VaultDeployData memory config = getVaultDeployData(env, network); + address vaulAddress = config.vault; + console.log("vaultAddress: ", vaulAddress); + IVault vault = IVault(vaulAddress); + // brokerId + bytes32[] memory allAllowedBroker = vault.getAllAllowedBroker(); + console2.log("allAllowedBroker length: ", allAllowedBroker.length); + for (uint256 i = 0; i < allAllowedBroker.length; i++) { + console2.logBytes32(allAllowedBroker[i]); + } + // token + bytes32[] memory allAllowedToken = vault.getAllAllowedToken(); + console2.log("allAllowedToken length: ", allAllowedToken.length); + for (uint256 i = 0; i < allAllowedToken.length; i++) { + console2.logBytes32(allAllowedToken[i]); + } + console.log("get allowed list done"); + } +} diff --git a/script/zip/Decode.s.sol b/script/zip/Decode.s.sol new file mode 100644 index 0000000..0308dd5 --- /dev/null +++ b/script/zip/Decode.s.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "forge-std/Script.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; +import "../../src/zip/OperatorManagerZip.sol"; +import "../utils/BaseScript.s.sol"; +import "../utils/ConfigHelper.s.sol"; + +contract Decode is BaseScript, ConfigHelper { + function run() external { + uint256 orderlyPrivateKey = vm.envUint("ORDERLY_PRIVATE_KEY"); + Envs memory envs = getEnvs(); + string memory env = envs.env; + string memory network = envs.ledgerNetwork; + ZipDeployData memory config = getZipDeployData(env, network); + address zipAddress = config.zip; + console.log("zip proxy address: ", zipAddress); + + IOperatorManagerZip zip = IOperatorManagerZip(zipAddress); + + vm.startBroadcast(orderlyPrivateKey); + // how to covert hex to bytes? + bytes memory data = bytes( + "0x6020402c5fe96ca3d61a0e9b704c4de52049065f51233a53f2187dee9002889f44a21199425e4a1cda60595e3464527f504a752e91c8f7193b91195e2ef9e3e0a6f3664a0100611c001c4105b4006164000062c000001b7e6493280b058c1f4966f1f27cf12331926319b4ac2872486c07e8fa2df82464429dd8a25fadc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d46610bd65eaca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa001a420bebc2006230c7801b420986f70060c01944c2e09f3c6c006136e50068179773d4af5260cc000067018bcd32d1a300001c7e068f50fbfad7cb6b6dd1aee2a1fc667888285bfb7365a7bf8570fe8d1d06675f07a2adc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d4661400b5fd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa5efffffffffffffffffffffffffffffffffffffffffffffffffffffffff4143e005fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffcf38801b420986f70061077f1944c2e09f3c6c006136e60068179773d4af5260cc000067018bcd32d1a300001b7e0193280b058c1f4966f1f27cf12331926319b4ac2872486c07e8fa2df82464429dd8a25fadc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d46610bd65eaca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa001a420bebc20062304cd80064096f0230001d40be001844c2e09f3c6c006136e70068179774f6eb185f4f000067018bcd45d6f200001c7e068f50fbfad7cb6b6dd1aee2a1fc667888285bfb7365a7bf8570fe8d1d06675f07a2adc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d4661400b5fd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa5efffffffffffffffffffffffffffffffffffffffffffffffffffffffff4143e0040ff5effffffffffffffffffffffffffffffffffffffffffffffffffffffffcfb32863096f02300062076c001844c2e09f3c6c006136e80068179774f6eb185f4f000067018bcd45d6f200001b7e01068f50fbfad7cb6b6dd1aee2a1fc667888285bfb7365a7bf8570fe8d1d065f6707a2adc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d4641610b5fd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa5efffffffffffffffffffffffffffffffffffffffffffffffffffffffff4143e0040ff5effffffffffffffffffffffffffffffffffffffffffffffffffffffffcfd7e0630967d64000620766001844c2e09f3c6c006136e90068179774f6ff9b0f55000067018bcd45d84a00001b7e0193280b058c1f4966f1f27cf12331926319b4ac2872486c07e8fa2df82464429dd8a25fadc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d46610bd65eaca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa001a420bebc2006230282000640967d640001d40be001844c2e09f3c6c006136ea0068179774f6ff9b0f55000067018bcd45d84a00001c7e068f50fbfad7cb6b6dd1aee2a1fc667888285bfb7365a7bf8570fe8d1d06675f07a2adc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d4661400b5fd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa5efffffffffffffffffffffffffffffffffffffffffffffffffffffffff4143e0040ff5effffffffffffffffffffffffffffffffffffffffffffffffffffffffcfb32863096f02300062076c001844c2e09f3c6c006136eb0068179774f71378c1f9000067018bcd45d99800001b7e0193280b058c1f4966f1f27cf12331926319b4ac2872486c07e8fa2df82464429dd8a25fadc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d46610bd65eaca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa001a420bebc20062304cd80064096f0230001d40be001844c2e09f3c6c006136ec0068179774f71378c1f9000067018bcd45d99800001c7e93280b058c1f4966f1f27cf12331926319b4ac2872486c07e8fa2df824649d41d8a25fadc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d46610bd65eaca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa001a420bebc20062304cd80064096f0230001d40be001844c2e09f3c6c006136ed0068179774f72733de55000067018bcd45dae300001c7e068f50fbfad7cb6b6dd1aee2a1fc667888285bfb7365a7bf8570fe8d1d06675f07a2adc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d4661400b5fd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa5efffffffffffffffffffffffffffffffffffffffffffffffffffffffff4143e0040ff5effffffffffffffffffffffffffffffffffffffffffffffffffffffffcfb32863096f02300062076c001844c2e09f3c6c006136ee0068179774f72733de55000067018bcd45dae300001b7e0193280b058c1f4966f1f27cf12331926319b4ac2872486c07e8fa2df82464429dd8a25fadc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d46610bd65eaca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa001a420bebc20062304cd80064096f0230001d40be001844c2e09f3c6c006136ef0068179774f73bc76916000067018bcd45dc3c00001c7e068f50fbfad7cb6b6dd1aee2a1fc667888285bfb7365a7bf8570fe8d1d06675f07a2adc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d4661400b5fd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa5efffffffffffffffffffffffffffffffffffffffffffffffffffffffff4143e0040ff5effffffffffffffffffffffffffffffffffffffffffffffffffffffffcfb32863096f02300062076c001844c2e09f3c6c006136f00068179774f73bc76916000067018bcd45dc3c00001b7e0193280b058c1f4966f1f27cf12331926319b4ac2872486c07e8fa2df82464429dd8a25fadc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d46610bd65eaca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa001a420bebc20062304cd80064096f0230001d40be001844c2e09f3c6c006136f10068179774f75120ff8d000067018bcd45dda200001c7e068f50fbfad7cb6b6dd1aee2a1fc667888285bfb7365a7bf8570fe8d1d06675f07a2adc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d4661400b5fd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa5efffffffffffffffffffffffffffffffffffffffffffffffffffffffff4143e0040ff5effffffffffffffffffffffffffffffffffffffffffffffffffffffffcfb32863096f02300062076c001844c2e09f3c6c006136f20068179774f75120ff8d000067018bcd45dda200001b7e0193280b058c1f4966f1f27cf12331926319b4ac2872486c07e8fa2df82464429dd8a25fadc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d46610bd65eaca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa001a420bebc20062304cd80064096f0230001d40be001844c2e09f3c6c006136f30068179774f765d5015a000067018bcd45defd00001c7e068f50fbfad7cb6b6dd1aee2a1fc667888285bfb7365a7bf8570fe8d1d06675f07a2adc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d4661400b5fd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa5efffffffffffffffffffffffffffffffffffffffffffffffffffffffff4143e0040ff5effffffffffffffffffffffffffffffffffffffffffffffffffffffffcfb32863096f02300062076c001844c2e09f3c6c006136f40068179774f765d5015a000067018bcd45defd00001b7e0193280b058c1f4966f1f27cf12331926319b4ac2872486c07e8fa2df82464429dd8a25fadc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d46610bd65eaca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa001a420bebc20062304cd80064096f0230001d40be001844c2e09f3c6c006136f50068179774f779efb9be000067018bcd45e04f00001c7e068f50fbfad7cb6b6dd1aee2a1fc667888285bfb7365a7bf8570fe8d1d06675f07a2adc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d4661400b5fd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa5efffffffffffffffffffffffffffffffffffffffffffffffffffffffff4143e0040ff5effffffffffffffffffffffffffffffffffffffffffffffffffffffffcfb32863096f02300062076c001844c2e09f3c6c006136f60068179774f779efb9be000067018bcd45e04f00001b7e0193280b058c1f4966f1f27cf12331926319b4ac2872486c07e8fa2df82464429dd8a25fadc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d46610bd65eaca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa001a420bebc20062304cd80064096f0230001d40be001844c2e09f3c6c006136f70068179774f78ed760c5000067018bcd45e1ad00001c7e068f50fbfad7cb6b6dd1aee2a1fc667888285bfb7365a7bf8570fe8d1d06675f07a2adc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d4661400b5fd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa5efffffffffffffffffffffffffffffffffffffffffffffffffffffffff4143e0040ff5effffffffffffffffffffffffffffffffffffffffffffffffffffffffcfb32863096f02300062076c001844c2e09f3c6c006136f80068179774f78ed760c5000067018bcd45e1ad00001b7e0193280b058c1f4966f1f27cf12331926319b4ac2872486c07e8fa2df82464429dd8a25fadc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d46610bd65eaca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa001a420bebc20062304cd80064096f0230001d40be001844c2e09f3c6c006136f90068179774f7a42189e0000067018bcd45e31300001c7e068f50fbfad7cb6b6dd1aee2a1fc667888285bfb7365a7bf8570fe8d1d06675f07a2adc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d4661400b5fd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa5efffffffffffffffffffffffffffffffffffffffffffffffffffffffff4143e0040ff5effffffffffffffffffffffffffffffffffffffffffffffffffffffffcfb32863096f02300062076c001844c2e09f3c6c006136fa0068179774f7a42189e0000067018bcd45e31300001b7e0193280b058c1f4966f1f27cf12331926319b4ac2872486c07e8fa2df82464429dd8a25fadc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d46610bd65eaca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa001a420bebc20062304cd80064096f0230001d40be001844c2e09f3c6c006136fb0068179774f7b84eda1a000067018bcd45e46500001c7e068f50fbfad7cb6b6dd1aee2a1fc667888285bfb7365a7bf8570fe8d1d06675f07a2adc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d4661400b5fd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa5efffffffffffffffffffffffffffffffffffffffffffffffffffffffff4143e0040ff5effffffffffffffffffffffffffffffffffffffffffffffffffffffffcfb32863096f02300062076c001844c2e09f3c6c006136fc0068179774f7b84eda1a000067018bcd45e46500001b7e0193280b058c1f4966f1f27cf12331926319b4ac2872486c07e8fa2df82464429dd8a25fadc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d46610bd65eaca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa001a420bebc20062304cd80064096f0230001d40be001844c2e09f3c6c006136fd0068179774f7cd581439000067018bcd45e5c600001c7e068f50fbfad7cb6b6dd1aee2a1fc667888285bfb7365a7bf8570fe8d1d06675f07a2adc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d4661400b5fd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa5efffffffffffffffffffffffffffffffffffffffffffffffffffffffff4143e0040ff5effffffffffffffffffffffffffffffffffffffffffffffffffffffffcfb32863096f02300062076c001844c2e09f3c6c006136fe0068179774f7cd581439000067018bcd45e5c600001b7e0193280b058c1f4966f1f27cf12331926319b4ac2872486c07e8fa2df82464429dd8a25fadc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d46610bd65eaca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa001a420bebc20062304cd80064096f0230001d40be001844c2e09f3c6c006136ff0068179774f7e2a24ba8000067018bcd45e72b00001c7e068f50fbfad7cb6b6dd1aee2a1fc667888285bfb7365a7bf8570fe8d1d06675f07a2adc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d4661400b5fd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa5efffffffffffffffffffffffffffffffffffffffffffffffffffffffff4143e0040ff5effffffffffffffffffffffffffffffffffffffffffffffffffffffffcfb32863096f02300062076c001844c2e09f3c6c006137000068179774f7e2a24ba8000067018bcd45e72b00001b7e0193280b058c1f4966f1f27cf12331926319b4ac2872486c07e8fa2df82464429dd8a25fadc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d46610bd65eaca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa001a420bebc20062304cd80064096f0230001d40be001844c2e09f3c6c006137010068179774f7f6de3d4b000067018bcd45e87f00001c7e068f50fbfad7cb6b6dd1aee2a1fc667888285bfb7365a7bf8570fe8d1d06675f07a2adc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d4661400b5fd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa5efffffffffffffffffffffffffffffffffffffffffffffffffffffffff4143e0040ff5effffffffffffffffffffffffffffffffffffffffffffffffffffffffcfb32863096f02300062076c001844c2e09f3c6c006137020068179774f7f6de3d4b000067018bcd45e87f00001b7e0193280b058c1f4966f1f27cf12331926319b4ac2872486c07e8fa2df82464429dd8a25fadc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d46610bd65eaca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa001a420bebc20062304cd80064096f0230001d40be001844c2e09f3c6c006137030068179774f80bc3c3c7000067018bcd45e9dd00001c7e068f50fbfad7cb6b6dd1aee2a1fc667888285bfb7365a7bf8570fe8d1d06675f07a2adc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d4661400b5fd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa5efffffffffffffffffffffffffffffffffffffffffffffffffffffffff4143e0040ff5effffffffffffffffffffffffffffffffffffffffffffffffffffffffcfb32863096f02300062076c001844c2e09f3c6c006137040068179774f80bc3c3c7000067018bcd45e9dd00001b7e0193280b058c1f4966f1f27cf12331926319b4ac2872486c07e8fa2df82464429dd8a25fadc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d46610bd65eaca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa001a420bebc20062304cd80064096f0230001d40be001844c2e09f3c6c006137050068179774f820e86e7f000067018bcd45eb4000001c7e068f50fbfad7cb6b6dd1aee2a1fc667888285bfb7365a7bf8570fe8d1d06675f07a2adc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d4661400b5fd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa5efffffffffffffffffffffffffffffffffffffffffffffffffffffffff4143e0040ff5effffffffffffffffffffffffffffffffffffffffffffffffffffffffcfb32863096f02300062076c001844c2e09f3c6c006137060068179774f820e86e7f000067018bcd45eb4000001b7e0193280b058c1f4966f1f27cf12331926319b4ac2872486c07e8fa2df82464429dd8a25fadc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d46610bd65eaca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa001a420bebc20062304cd80064096f0230001d40be001844c2e09f3c6c006137070068179774f836351da4000067018bcd45eca500001c7e068f50fbfad7cb6b6dd1aee2a1fc667888285bfb7365a7bf8570fe8d1d06675f07a2adc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d4661400b5fd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa5efffffffffffffffffffffffffffffffffffffffffffffffffffffffff4143e0040ff5effffffffffffffffffffffffffffffffffffffffffffffffffffffffcfb32863096f02300062076c001844c2e09f3c6c006137080068179774f836351da4000067018bcd45eca500001b7e0193280b058c1f4966f1f27cf12331926319b4ac2872486c07e8fa2df82464429dd8a25fadc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d46610bd65eaca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa001a420bebc20062304cd80064096f0230001d40be001844c2e09f3c6c006137090068179774f84a7649b8000067018bcd45edf900001c7e068f50fbfad7cb6b6dd1aee2a1fc667888285bfb7365a7bf8570fe8d1d06675f07a2adc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d4661400b5fd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa5efffffffffffffffffffffffffffffffffffffffffffffffffffffffff4143e0040ff5effffffffffffffffffffffffffffffffffffffffffffffffffffffffcfb32863096f02300062076c001844c2e09f3c6c0061370a0068179774f84a7649b8000067018bcd45edf900001b7e0193280b058c1f4966f1f27cf12331926319b4ac2872486c07e8fa2df82464429dd8a25fadc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d46610bd65eaca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa001a420bebc20062304cd80064096f0230001d40be001844c2e09f3c6c0061370b0068179774f85e7ec780000067018bcd45ef4900001c7e068f50fbfad7cb6b6dd1aee2a1fc667888285bfb7365a7bf8570fe8d1d06675f07a2adc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d4661400b5fd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa5efffffffffffffffffffffffffffffffffffffffffffffffffffffffff4143e0040ff5effffffffffffffffffffffffffffffffffffffffffffffffffffffffcfb32863096f02300062076c001844c2e09f3c6c0061370c0068179774f85e7ec780000067018bcd45ef4900001b7e0193280b058c1f4966f1f27cf12331926319b4ac2872486c07e8fa2df82464429dd8a25fadc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d46610bd65eaca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa001a420bebc20062304cd80064096f0230001d40be001844c2e09f3c6c0061370d0068179774f872755d9c000067018bcd45f09800001c7e068f50fbfad7cb6b6dd1aee2a1fc667888285bfb7365a7bf8570fe8d1d06675f07a2adc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d4661400b5fd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa5efffffffffffffffffffffffffffffffffffffffffffffffffffffffff4143e0040ff5effffffffffffffffffffffffffffffffffffffffffffffffffffffffcfb32863096f02300062076c001844c2e09f3c6c0061370e0068179774f872755d9c000067018bcd45f09800001b7e0193280b058c1f4966f1f27cf12331926319b4ac2872486c07e8fa2df82464429dd8a25fadc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d46610bd65eaca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa001a420bebc20062304cd80064096f0230001d40be001844c2e09f3c6c0061370f0068179774f88610b24b000067018bcd45f1e100001c7e068f50fbfad7cb6b6dd1aee2a1fc667888285bfb7365a7bf8570fe8d1d06675f07a2adc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d4661400b5fd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa5efffffffffffffffffffffffffffffffffffffffffffffffffffffffff4143e0040ff5effffffffffffffffffffffffffffffffffffffffffffffffffffffffcfb32863096f02300062076c001844c2e09f3c6c006137100068179774f88610b24b000067018bcd45f1e100001b7e01068f50fbfad7cb6b6dd1aee2a1fc667888285bfb7365a7bf8570fe8d1d065f6707a2adc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d4641610b5fd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa5efffffffffffffffffffffffffffffffffffffffffffffffffffffffff4143e0040ff5effffffffffffffffffffffffffffffffffffffffffffffffffffffffcfb32863096f02300062076c001844c2e09f3c6c006137110068179774f899d35863000067018bcd45f32d00001b7e0193280b058c1f4966f1f27cf12331926319b4ac2872486c07e8fa2df82464429dd8a25fadc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d46610bd65eaca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa001a420bebc20062304cd80064096f0230001d40be001844c2e09f3c6c006137120068179774f899d35863000067018bcd45f32d00001c7e068f50fbfad7cb6b6dd1aee2a1fc667888285bfb7365a7bf8570fe8d1d06675f07a2adc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d4661400b5fd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa5efffffffffffffffffffffffffffffffffffffffffffffffffffffffff4143e0040ff5effffffffffffffffffffffffffffffffffffffffffffffffffffffffcfd7e0630967d64000620766001844c2e09f3c6c006137130068179774f8adb373eb000067018bcd45f47a00001b7e0193280b058c1f4966f1f27cf12331926319b4ac2872486c07e8fa2df82464429dd8a25fadc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d46610bd65eaca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa001a420bebc2006230282000640967d640001d40be001844c2e09f3c6c006137140068179774f8adb373eb000067018bcd45f47a00001c7e93280b058c1f4966f1f27cf12331926319b4ac2872486c07e8fa2df824649d41d8a25fadc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d46610bd65eaca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa001a420bebc200623046980064096dc9b0001d40be001844c2e09f3c6c006137150068179774f8c20e327d000067018bcd45f5d000001c7e068f50fbfad7cb6b6dd1aee2a1fc667888285bfb7365a7bf8570fe8d1d06675f07a2adc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d4661400b5fd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa5efffffffffffffffffffffffffffffffffffffffffffffffffffffffff4143e0040ff5effffffffffffffffffffffffffffffffffffffffffffffffffffffffcfb96863096dc9b00062076b001844c2e09f3c6c006137160068179774f8c20e327d000067018bcd45f5d000001b7e0193280b058c1f4966f1f27cf12331926319b4ac2872486c07e8fa2df82464429dd8a25fadc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d46610bd65eaca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa001a420bebc200623046980064096dc9b0001d40be001844c2e09f3c6c006137170068179774f8d6655664000067018bcd45f72500001c7e068f50fbfad7cb6b6dd1aee2a1fc667888285bfb7365a7bf8570fe8d1d06675f07a2adc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d4661400b5fd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa5efffffffffffffffffffffffffffffffffffffffffffffffffffffffff4143e0040ff5effffffffffffffffffffffffffffffffffffffffffffffffffffffffcfb96863096dc9b00062076b001844c2e09f3c6c006137180068179774f8d6655664000067018bcd45f72500001b7e0193280b058c1f4966f1f27cf12331926319b4ac2872486c07e8fa2df82464429dd8a25fadc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d46610bd65eaca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa001a420bebc200623046980064096dc9b0001d40be001844c2e09f3c6c006137190068179774f8ea6ae183000067018bcd45f87500001c7e068f50fbfad7cb6b6dd1aee2a1fc667888285bfb7365a7bf8570fe8d1d06675f07a2adc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d4661400b5fd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa5efffffffffffffffffffffffffffffffffffffffffffffffffffffffff4143e0040ff5effffffffffffffffffffffffffffffffffffffffffffffffffffffffcfb96863096dc9b00062076b001844c2e09f3c6c0061371a0068179774f8ea6ae183000067018bcd45f87500001b7e0193280b058c1f4966f1f27cf12331926319b4ac2872486c07e8fa2df82464429dd8a25fadc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d46610bd65eaca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa001a420bebc200623046980064096dc9b0001d40be001844c2e09f3c6c0061371b0068179774f8fe6fc028000067018bcd45f9c500001c7e068f50fbfad7cb6b6dd1aee2a1fc667888285bfb7365a7bf8570fe8d1d06675f07a2adc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d4661400b5fd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa5efffffffffffffffffffffffffffffffffffffffffffffffffffffffff4143e0040ff5effffffffffffffffffffffffffffffffffffffffffffffffffffffffcfb96863096dc9b00062076b001844c2e09f3c6c0061371c0068179774f8fe6fc028000067018bcd45f9c500001b7e0193280b058c1f4966f1f27cf12331926319b4ac2872486c07e8fa2df82464429dd8a25fadc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d46610bd65eaca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa001a420bebc200623046980064096dc9b0001d40be001844c2e09f3c6c0061371d0068179774f911d42e22000067018bcd45fb0a00001c7e068f50fbfad7cb6b6dd1aee2a1fc667888285bfb7365a7bf8570fe8d1d06675f07a2adc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d4661400b5fd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa5efffffffffffffffffffffffffffffffffffffffffffffffffffffffff4143e0040ff5effffffffffffffffffffffffffffffffffffffffffffffffffffffffcfb96863096dc9b00062076b001844c2e09f3c6c0061371e0068179774f911d42e22000067018bcd45fb0a00001b7e0193280b058c1f4966f1f27cf12331926319b4ac2872486c07e8fa2df82464429dd8a25fadc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d46610bd65eaca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa001a420bebc200623046980064096dc9b0001d40be001844c2e09f3c6c0061371f0068179774f92576ef1e000067018bcd45fc5300001c7e068f50fbfad7cb6b6dd1aee2a1fc667888285bfb7365a7bf8570fe8d1d06675f07a2adc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d4661400b5fd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa5efffffffffffffffffffffffffffffffffffffffffffffffffffffffff4143e0040ff5effffffffffffffffffffffffffffffffffffffffffffffffffffffffcfb96863096dc9b00062076b001844c2e09f3c6c006137200068179774f92576ef1e000067018bcd45fc5300001b7e0193280b058c1f4966f1f27cf12331926319b4ac2872486c07e8fa2df82464429dd8a25fadc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d46610bd65eaca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa001a420bebc200623046980064096dc9b0001d40be001844c2e09f3c6c006137210068179774f939b5477e000067018bcd45fda700001c7e068f50fbfad7cb6b6dd1aee2a1fc667888285bfb7365a7bf8570fe8d1d06675f07a2adc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d4661400b5fd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa5efffffffffffffffffffffffffffffffffffffffffffffffffffffffff4143e0040ff5effffffffffffffffffffffffffffffffffffffffffffffffffffffffcfb96863096dc9b00062076b001844c2e09f3c6c006137220068179774f939b5477e000067018bcd45fda700001b7e0193280b058c1f4966f1f27cf12331926319b4ac2872486c07e8fa2df82464429dd8a25fadc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d46610bd65eaca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa001a420bebc200623046980064096dc9b0001d40be001844c2e09f3c6c006137230068179774f94d749ca3000067018bcd45fef200001c7e068f50fbfad7cb6b6dd1aee2a1fc667888285bfb7365a7bf8570fe8d1d06675f07a2adc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d4661400b5fd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa5efffffffffffffffffffffffffffffffffffffffffffffffffffffffff4143e0040ff5effffffffffffffffffffffffffffffffffffffffffffffffffffffffcfb96863096dc9b00062076b001844c2e09f3c6c006137240068179774f94d749ca3000067018bcd45fef200001b7e0193280b058c1f4966f1f27cf12331926319b4ac2872486c07e8fa2df82464429dd8a25fadc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d46610bd65eaca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa001a420bebc200623046980064096dc9b0001d40be001844c2e09f3c6c006137250068179774f961bb0ff6000067018bcd46004600001c7e068f50fbfad7cb6b6dd1aee2a1fc667888285bfb7365a7bf8570fe8d1d06675f07a2adc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d4661400b5fd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa5efffffffffffffffffffffffffffffffffffffffffffffffffffffffff4143e0040ff5effffffffffffffffffffffffffffffffffffffffffffffffffffffffcfb96863096dc9b00062076b001844c2e09f3c6c006137260068179774f961bb0ff6000067018bcd46004600001b7e0193280b058c1f4966f1f27cf12331926319b4ac2872486c07e8fa2df82464429dd8a25fadc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d46610bd65eaca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa001a420bebc200623046980064096dc9b0001d40be001844c2e09f3c6c006137270068179774f9756b946d000067018bcd46019100001c7e068f50fbfad7cb6b6dd1aee2a1fc667888285bfb7365a7bf8570fe8d1d06675f07a2adc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d4661400b5fd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa5efffffffffffffffffffffffffffffffffffffffffffffffffffffffff4143e0040ff5effffffffffffffffffffffffffffffffffffffffffffffffffffffffcfb96863096dc9b00062076b001844c2e09f3c6c006137280068179774f9756b946d000067018bcd46019100001b7e01068f50fbfad7cb6b6dd1aee2a1fc667888285bfb7365a7bf8570fe8d1d065f6707a2adc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d4641610b5fd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa5efffffffffffffffffffffffffffffffffffffffffffffffffffffffff4143e0040ff5effffffffffffffffffffffffffffffffffffffffffffffffffffffffcfb96863096dc9b00062076b001844c2e09f3c6c006137290068179774f989835c99000067018bcd4602e200001b7e0193280b058c1f4966f1f27cf12331926319b4ac2872486c07e8fa2df82464429dd8a25fadc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d46610bd65eaca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa001a420bebc200623046980064096dc9b0001d40be001844c2e09f3c6c0061372a0068179774f989835c99000067018bcd4602e200001c7e93280b058c1f4966f1f27cf12331926319b4ac2872486c07e8fa2df824649d41d8a25fadc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d46610bd65eaca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa001a420bebc200623046980064096dc9b0001d40be001844c2e09f3c6c0061372b0068179774f99e06726c000067018bcd46043a00001c7e068f50fbfad7cb6b6dd1aee2a1fc667888285bfb7365a7bf8570fe8d1d06675f07a2adc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d4661400b5fd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa5efffffffffffffffffffffffffffffffffffffffffffffffffffffffff4143e0040ff5effffffffffffffffffffffffffffffffffffffffffffffffffffffffcfb96863096dc9b00062076b001844c2e09f3c6c0061372c0068179774f99e06726c000067018bcd46043a00001b7e0193280b058c1f4966f1f27cf12331926319b4ac2872486c07e8fa2df82464429dd8a25fadc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d46610bd65eaca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa001a420bebc200623046980064096dc9b0001d40be001844c2e09f3c6c0061372d0068179774f9b26079f5000067018bcd46058f00001c7e068f50fbfad7cb6b6dd1aee2a1fc667888285bfb7365a7bf8570fe8d1d06675f07a2adc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d4661400b5fd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa5efffffffffffffffffffffffffffffffffffffffffffffffffffffffff4143e0040ff5effffffffffffffffffffffffffffffffffffffffffffffffffffffffcfb96863096dc9b00062076b001844c2e09f3c6c0061372e0068179774f9b26079f5000067018bcd46058f00001b7e0193280b058c1f4966f1f27cf12331926319b4ac2872486c07e8fa2df82464429dd8a25fadc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d46610bd65eaca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa001a420bebc200623046980064096dc9b0001d40be001844c2e09f3c6c0061372f0068179774f9c69aa5d5000067018bcd4606e300001c7e068f50fbfad7cb6b6dd1aee2a1fc667888285bfb7365a7bf8570fe8d1d06675f07a2adc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d4661400b5fd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa5efffffffffffffffffffffffffffffffffffffffffffffffffffffffff4143e0040ff5effffffffffffffffffffffffffffffffffffffffffffffffffffffffcfb96863096dc9b00062076b001844c2e09f3c6c006137300068179774f9c69aa5d5000067018bcd4606e300001b7e0193280b058c1f4966f1f27cf12331926319b4ac2872486c07e8fa2df82464429dd8a25fadc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d46610bd65eaca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa001a420bebc200623046980064096dc9b0001d40be001844c2e09f3c6c006137310068179774f9da87ff61000067018bcd46083100001c7e068f50fbfad7cb6b6dd1aee2a1fc667888285bfb7365a7bf8570fe8d1d06675f07a2adc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d4661400b5fd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa5efffffffffffffffffffffffffffffffffffffffffffffffffffffffff4143e0040ff5effffffffffffffffffffffffffffffffffffffffffffffffffffffffcfb96863096dc9b00062076b001844c2e09f3c6c006137320068179774f9da87ff61000067018bcd46083100001b7e0193280b058c1f4966f1f27cf12331926319b4ac2872486c07e8fa2df82464429dd8a25fadc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d46610bd65eaca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa001a420bebc200623046980064096dc9b0001d40be001844c2e09f3c6c006137331745179774f9ee3b00401a65018bcd46097c1e7e068f50fbfad7cb6b6dd1aee2a1fc667888285bfb7365a7bf8570fe8d1d06675f07a2adc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d4661400b5fd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa5efffffffffffffffffffffffffffffffffffffffffffffffffffffffff4143e0040ff5effffffffffffffffffffffffffffffffffffffffffffffffffffffffcfb96863096dc9b00062076b001844c2e09f3c6c006137341745179774f9ee3b00401a65018bcd46097c1d7e0193280b058c1f4966f1f27cf12331926319b4ac2872486c07e8fa2df82464429dd8a25fadc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d46610bd65eaca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa001a420bebc200623046980064096dc9b0001d40be001844c2e09f3c6c006137350068179774fa0248128e000067018bcd460acc00001c7e068f50fbfad7cb6b6dd1aee2a1fc667888285bfb7365a7bf8570fe8d1d06675f07a2adc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d4661400b5fd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa5efffffffffffffffffffffffffffffffffffffffffffffffffffffffff4143e0040ff5effffffffffffffffffffffffffffffffffffffffffffffffffffffffcfb96863096dc9b00062076b001844c2e09f3c6c006137360068179774fa0248128e000067018bcd460acc00001b7e0193280b058c1f4966f1f27cf12331926319b4ac2872486c07e8fa2df82464429dd8a25fadc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d46610bd65eaca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa001a420bebc200623046980064096dc9b0001d40be001844c2e09f3c6c006137370068179774fa15f964ab000067018bcd460c1600001c7e068f50fbfad7cb6b6dd1aee2a1fc667888285bfb7365a7bf8570fe8d1d06675f07a2adc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d4661400b5fd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa5efffffffffffffffffffffffffffffffffffffffffffffffffffffffff4143e0040ff5effffffffffffffffffffffffffffffffffffffffffffffffffffffffcfb96863096dc9b00062076b001844c2e09f3c6c006137380068179774fa15f964ab000067018bcd460c1600001b7e0193280b058c1f4966f1f27cf12331926319b4ac2872486c07e8fa2df82464429dd8a25fadc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d46610bd65eaca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa001a420bebc20062303a180064096b58b0001d40be001844c2e09f3c6c006137390068179774fa2a4362cb000067018bcd460d6b00001c7e068f50fbfad7cb6b6dd1aee2a1fc667888285bfb7365a7bf8570fe8d1d06675f07a2adc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d4661400b5fd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa5efffffffffffffffffffffffffffffffffffffffffffffffffffffffff4143e0040ff5effffffffffffffffffffffffffffffffffffffffffffffffffffffffcfc5e863096b58b000620769001844c2e09f3c6c0061373a0068179774fa2a4362cb000067018bcd460d6b00001b7e01068f50fbfad7cb6b6dd1aee2a1fc667888285bfb7365a7bf8570fe8d1d065f6707a2adc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d4641610b5fd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa5efffffffffffffffffffffffffffffffffffffffffffffffffffffffff4143e0040ff5effffffffffffffffffffffffffffffffffffffffffffffffffffffffcfd5886309684b7000620766001844c2e09f3c6c0061373b0068179774fa3ebc2a3f000067018bcd460ec200001b7e0193280b058c1f4966f1f27cf12331926319b4ac2872486c07e8fa2df82464429dd8a25fadc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d46610bd65eaca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa001a420bebc20062302a78006409684b70001d40be001844c2e09f3c6c0061373c0068179774fa3ebc2a3f000067018bcd460ec200001c7e93280b058c1f4966f1f27cf12331926319b4ac2872486c07e8fa2df824649d41d8a25fadc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d46610bd65eaca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa001a420bebc200623043780064096d2d70001d40be001844c2e09f3c6c0061373d0068179774fa527e3284000067018bcd46100e00001c7e068f50fbfad7cb6b6dd1aee2a1fc667888285bfb7365a7bf8570fe8d1d06675f07a2adc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d4661400b5fd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa5efffffffffffffffffffffffffffffffffffffffffffffffffffffffff4143e0040ff5effffffffffffffffffffffffffffffffffffffffffffffffffffffffcfbc8863096d2d700062076a001844c2e09f3c6c0061373e0068179774fa527e3284000067018bcd46100e00001b7e0193280b058c1f4966f1f27cf12331926319b4ac2872486c07e8fa2df82464429dd8a25fadc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d46610bd65eaca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa001a420bebc200623043780064096d2d70001d40be001844c2e09f3c6c0061373f0068179774fa66a5cd9f000067018bcd46116000001c7e068f50fbfad7cb6b6dd1aee2a1fc667888285bfb7365a7bf8570fe8d1d06675f07a2adc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d4661400b5fd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa5efffffffffffffffffffffffffffffffffffffffffffffffffffffffff4143e0040ff5effffffffffffffffffffffffffffffffffffffffffffffffffffffffcfbc8863096d2d700062076a001844c2e09f3c6c006137400068179774fa66a5cd9f000067018bcd46116000001b7e0193280b058c1f4966f1f27cf12331926319b4ac2872486c07e8fa2df82464429dd8a25fadc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d46610bd65eaca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa001a420bebc200623043780064096d2d70001d40be001844c2e09f3c6c006137410068179774fa7abacc7e000067018bcd4612b100001c7e068f50fbfad7cb6b6dd1aee2a1fc667888285bfb7365a7bf8570fe8d1d06675f07a2adc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d4661400b5fd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa5efffffffffffffffffffffffffffffffffffffffffffffffffffffffff4143e0040ff5effffffffffffffffffffffffffffffffffffffffffffffffffffffffcfbc8863096d2d700062076a001844c2e09f3c6c006137420068179774fa7abacc7e000067018bcd4612b100001b7e0193280b058c1f4966f1f27cf12331926319b4ac2872486c07e8fa2df82464429dd8a25fadc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d46610bd65eaca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa001a420bebc200623043780064096d2d70001d40be001844c2e09f3c6c006137430068179774fa8f2cd0c5000067018bcd46140800001c7e068f50fbfad7cb6b6dd1aee2a1fc667888285bfb7365a7bf8570fe8d1d06675f07a2adc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d4661400b5fd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa5efffffffffffffffffffffffffffffffffffffffffffffffffffffffff4143e0040ff5effffffffffffffffffffffffffffffffffffffffffffffffffffffffcfbc8863096d2d700062076a001844c2e09f3c6c006137440068179774fa8f2cd0c5000067018bcd46140800001b7e0193280b058c1f4966f1f27cf12331926319b4ac2872486c07e8fa2df82464429dd8a25fadc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d46610bd65eaca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa001a420bebc200623043780064096d2d70001d40be001844c2e09f3c6c006137450068179774faa2a856b4000067018bcd46154f00001c7e068f50fbfad7cb6b6dd1aee2a1fc667888285bfb7365a7bf8570fe8d1d06675f07a2adc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d4661400b5fd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa5efffffffffffffffffffffffffffffffffffffffffffffffffffffffff4143e0040ff5effffffffffffffffffffffffffffffffffffffffffffffffffffffffcfbc8863096d2d700062076a001844c2e09f3c6c006137460068179774faa2a856b4000067018bcd46154f00001b7e0193280b058c1f4966f1f27cf12331926319b4ac2872486c07e8fa2df82464429dd8a25fadc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d46610bd65eaca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa001a420bebc200623043780064096d2d70001d40be001844c2e09f3c6c006137470068179774fab6928293000067018bcd46169d00001c7e068f50fbfad7cb6b6dd1aee2a1fc667888285bfb7365a7bf8570fe8d1d06675f07a2adc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d4661400b5fd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa5efffffffffffffffffffffffffffffffffffffffffffffffffffffffff4143e0040ff5effffffffffffffffffffffffffffffffffffffffffffffffffffffffcfbc8863096d2d700062076a001844c2e09f3c6c006137480068179774fab6928293000067018bcd46169d00001c4001" + ); + console.logBytes(data); + zip.decodeFuturesTradeUploadData(data); + vm.stopBroadcast(); + console.log("decodeFuturesTradeUploadData done"); + } +} diff --git a/script/zip/DeployNewZip.s.sol b/script/zip/DeployNewZip.s.sol new file mode 100644 index 0000000..f5ae92d --- /dev/null +++ b/script/zip/DeployNewZip.s.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "forge-std/Script.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; +import "../../src/zip/OperatorManagerZip.sol"; +import "../utils/BaseScript.s.sol"; +import "../utils/ConfigHelper.s.sol"; + +contract DeployNewZip is BaseScript, ConfigHelper { + function run() external { + uint256 orderlyPrivateKey = vm.envUint("ORDERLY_PRIVATE_KEY"); + Envs memory envs = getEnvs(); + string memory env = envs.env; + string memory network = envs.ledgerNetwork; + + ZipDeployData memory config = getZipDeployData(env, network); + address zipAddress = config.zip; + console.log("Zip address: ", zipAddress); + + vm.startBroadcast(orderlyPrivateKey); + IOperatorManagerZip zipImpl = new OperatorManagerZip(); + console.log("new zip implementation: ", address(zipImpl)); + vm.stopBroadcast(); + console.log("deploy done"); + } +} diff --git a/script/zip/DeployProxyZip.s.sol b/script/zip/DeployProxyZip.s.sol new file mode 100644 index 0000000..58bb5ac --- /dev/null +++ b/script/zip/DeployProxyZip.s.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "forge-std/Script.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; +import "../../src/zip/OperatorManagerZip.sol"; + +import "../utils/BaseScript.s.sol"; +import "../utils/ConfigHelper.s.sol"; + +contract DeployLedger is BaseScript, ConfigHelper { + string env; + string network; + + function run() external { + uint256 orderlyPrivateKey = vm.envUint("ORDERLY_PRIVATE_KEY"); + + Envs memory envs = getEnvs(); + env = envs.env; + network = envs.ledgerNetwork; + LedgerDeployData memory ledgerConfig = getLedgerDeployData(env, network); + address operatorManager = ledgerConfig.operatorManager; + address proxyAdmin = ledgerConfig.proxyAdmin; + vm.startBroadcast(orderlyPrivateKey); + + ProxyAdmin admin = ProxyAdmin(proxyAdmin); + + // avoid stack too deep error + { + console.log("proxyAdmin address: ", address(admin)); + } + + IOperatorManagerZip zipImpl = new OperatorManagerZip(); + + bytes memory initData = abi.encodeWithSignature("initialize()"); + + TransparentUpgradeableProxy zipProxy = + new TransparentUpgradeableProxy(address(zipImpl), address(admin), initData); + + // avoid stack too deep error + { + console.log("deployed operateManagerZip proxy address: ", address(zipProxy)); + writeZipDeployData(env, network, "zip", vm.toString(address(zipProxy))); + console.log("deployed success"); + } + + IOperatorManagerZip operatorManagerZip = IOperatorManagerZip(address(zipProxy)); + + // avoid stack too deep error + { + console.log("operatorManager address: ", operatorManager); + operatorManagerZip.setOpeartorManager(operatorManager); + operatorManagerZip.initSymbolId2Hash(); + } + vm.stopBroadcast(); + + console.log("All done!"); + } +} diff --git a/script/zip/SetOperatorAddress.s.sol b/script/zip/SetOperatorAddress.s.sol new file mode 100644 index 0000000..2abe8dd --- /dev/null +++ b/script/zip/SetOperatorAddress.s.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "forge-std/Script.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; +import "../../src/zip/OperatorManagerZip.sol"; +import "../utils/BaseScript.s.sol"; +import "../utils/ConfigHelper.s.sol"; + +contract SetOperatorAddress is BaseScript, ConfigHelper { + function run() external { + uint256 orderlyPrivateKey = vm.envUint("ORDERLY_PRIVATE_KEY"); + Envs memory envs = getEnvs(); + string memory env = envs.env; + string memory network = envs.ledgerNetwork; + LedgerDeployData memory ledgerConfig = getLedgerDeployData(env, network); + address operatorAddress = ledgerConfig.operatorAddress; + ZipDeployData memory config = getZipDeployData(env, network); + address zipAddress = config.zip; + + console.log("zip proxy address: ", zipAddress); + console.log("operator address: ", operatorAddress); + IOperatorManagerZip zip = IOperatorManagerZip(zipAddress); + + vm.startBroadcast(orderlyPrivateKey); + zip.setOperator(operatorAddress); + vm.stopBroadcast(); + console.log("Set Operator address done"); + } +} diff --git a/script/zip/TransferOwner.s.sol b/script/zip/TransferOwner.s.sol new file mode 100644 index 0000000..2751dd8 --- /dev/null +++ b/script/zip/TransferOwner.s.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "forge-std/Script.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; +import "../../src/zip/OperatorManagerZip.sol"; +import "../utils/BaseScript.s.sol"; +import "../utils/ConfigHelper.s.sol"; + +contract TransferOwner is BaseScript, ConfigHelper { + function run() external { + uint256 orderlyPrivateKey = vm.envUint("ORDERLY_PRIVATE_KEY"); + Envs memory envs = getEnvs(); + string memory env = envs.env; + string memory network = envs.ledgerNetwork; + + ZipDeployData memory config = getZipDeployData(env, network); + address zipAddress = config.zip; + console.log("Zip address: ", zipAddress); + + LedgerDeployData memory ledgerConfig = getLedgerDeployData(env, network); + address multiSigAddress = ledgerConfig.multiSig; + console.log("multiSigAddress: ", multiSigAddress); + + vm.startBroadcast(orderlyPrivateKey); + + { + // change the owner of the impls + OperatorManagerZip zip = OperatorManagerZip(zipAddress); + zip.transferOwnership(multiSigAddress); + } + + vm.stopBroadcast(); + console.log("transfer owner done"); + } +} diff --git a/script/zip/UpgradeZip.s.sol b/script/zip/UpgradeZip.s.sol new file mode 100644 index 0000000..b70a8d9 --- /dev/null +++ b/script/zip/UpgradeZip.s.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "forge-std/Script.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; +import "../../src/zip/OperatorManagerZip.sol"; +import "../utils/BaseScript.s.sol"; +import "../utils/ConfigHelper.s.sol"; + +contract UpgradeOperatorManager is BaseScript, ConfigHelper { + function run() external { + uint256 orderlyPrivateKey = vm.envUint("ORDERLY_PRIVATE_KEY"); + Envs memory envs = getEnvs(); + string memory env = envs.env; + string memory network = envs.ledgerNetwork; + LedgerDeployData memory ledgerConfig = getLedgerDeployData(env, network); + address adminAddress = ledgerConfig.proxyAdmin; + + ZipDeployData memory config = getZipDeployData(env, network); + address zipAddress = config.zip; + console.log("adminAddress: ", adminAddress); + console.log("zip proxy address: ", zipAddress); + + ProxyAdmin admin = ProxyAdmin(adminAddress); + ITransparentUpgradeableProxy zipProxy = ITransparentUpgradeableProxy(zipAddress); + + vm.startBroadcast(orderlyPrivateKey); + + IOperatorManagerZip zipImpl = new OperatorManagerZip(); + admin.upgrade(zipProxy, address(zipImpl)); + + vm.stopBroadcast(); + console.log("upgrade done"); + } +} diff --git a/src/CrossChainManager.sol b/src/CrossChainManager.sol deleted file mode 100644 index c93b9ac..0000000 --- a/src/CrossChainManager.sol +++ /dev/null @@ -1,59 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.18; - -import "./interface/ISettlement.sol"; -import "./interface/ICrossChainManager.sol"; -import "./interface/IOperatorManager.sol"; -import "openzeppelin-contracts/contracts/access/Ownable.sol"; - -/** - * CrossChainManager is responsible for executing cross-chain tx. - * This contract should only have one in main-chain (avalanche) - */ -contract CrossChainManager is ICrossChainManager, Ownable { - // settlement Interface - ISettlement public settlement; - // operatorManager Interface - IOperatorManager public operatorManager; - - // set settlement - function setSettlement(address _settlement) public onlyOwner { - settlement = ISettlement(_settlement); - } - - // set operatorManager - function setOperatorManager(address _operatorManager) public onlyOwner { - operatorManager = IOperatorManager(_operatorManager); - } - - // cross-chain operator deposit - // TODO should be removed - function crossChainOperatorExecuteAction( - OperatorTypes.CrossChainOperatorActionData actionData, - bytes calldata action - ) public override onlyOwner { - if (actionData == OperatorTypes.CrossChainOperatorActionData.UserDeposit) { - // UserDeposit - settlement.accountDeposit(abi.decode(action, (AccountTypes.AccountDeposit))); - } else if (actionData == OperatorTypes.CrossChainOperatorActionData.UserEmergencyWithdraw) { - // UserEmergencyWithdraw iff cefi down - require(operatorManager.checkCefiDown(), "cefi not down"); - // TODO - // settlement.accountEmergencyWithdraw(abi.decode(action, (PerpTypes.WithdrawData))); - } else { - revert("invalid action data"); - } - } - - function deposit(CrossChainMessageTypes.MessageV1 calldata message) public override onlyOwner { - // convert message to AccountTypes.AccountDeposit - AccountTypes.AccountDeposit memory data = AccountTypes.AccountDeposit({ - accountId: message.accountId, - addr: message.addr, - symbol: message.tokenSymbol, - amount: message.tokenAmount, - chainId: message.srcChainId - }); - settlement.accountDeposit(data); - } -} diff --git a/src/FeeManager.sol b/src/FeeManager.sol new file mode 100644 index 0000000..54a80f3 --- /dev/null +++ b/src/FeeManager.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "./interface/IFeeManager.sol"; +import "./LedgerComponent.sol"; + +/// @title FeeManager component for Ledger contract +/// @author Orderly_Rubick +/// @notice FeeManager saves FeeCollector accountId, both getter and setter +contract FeeManager is IFeeManager, LedgerComponent { + // accountId + bytes32 public withdrawFeeCollector; + // accountId + bytes32 public futuresFeeCollector; + // broker fee accountId + mapping(bytes32 => bytes32) public brokerHash2BrokerAccountId; + + constructor() { + _disableInitializers(); + } + + function initialize() external override(IFeeManager, LedgerComponent) initializer { + __Ownable_init(); + // https://wootraders.atlassian.net/wiki/spaces/ORDER/pages/346882377/System+Account+-+V2 + futuresFeeCollector = 0x0ded76d9b80cba463c51e8d556fda7ae63458e8fc1d912ae87ecae5ceb4f5d03; + withdrawFeeCollector = 0xd24181b51223b8998dba9fd230a053034dd7d0140c3a50c57c806def77992663; + } + + /// @notice Get the fee collector account id according to the fee collector type + /// @param feeCollectorType The fee collector type + /// @return The fee collector account id + function getFeeCollector(FeeCollectorType feeCollectorType) external view override returns (bytes32) { + if (feeCollectorType == FeeCollectorType.WithdrawFeeCollector) { + return withdrawFeeCollector; + } else if (feeCollectorType == FeeCollectorType.FuturesFeeCollector) { + return futuresFeeCollector; + } + revert InvalidFeeCollectorType(); + } + + /// @notice Change the fee collector account id according to the fee collector type + /// @param feeCollectorType The fee collector type + /// @param _newCollector The new fee collector account id + function changeFeeCollector(FeeCollectorType feeCollectorType, bytes32 _newCollector) public override onlyOwner { + if (feeCollectorType == FeeCollectorType.WithdrawFeeCollector) { + emit ChangeFeeCollector(feeCollectorType, withdrawFeeCollector, _newCollector); + withdrawFeeCollector = _newCollector; + } else if (feeCollectorType == FeeCollectorType.FuturesFeeCollector) { + emit ChangeFeeCollector(feeCollectorType, futuresFeeCollector, _newCollector); + futuresFeeCollector = _newCollector; + } else { + revert InvalidFeeCollectorType(); + } + } + + /// @notice Set the broker fee account id + /// @param brokerHash The broker id + /// @param brokerAccountId The broker fee account id + function setBrokerAccountId(bytes32 brokerHash, bytes32 brokerAccountId) external override onlyOwner { + if (brokerHash == bytes32(0) || brokerAccountId == bytes32(0)) revert Bytes32Zero(); + emit ChangeBrokerAccountId(brokerHash2BrokerAccountId[brokerHash], brokerAccountId); + brokerHash2BrokerAccountId[brokerHash] = brokerAccountId; + } +} diff --git a/src/Ledger.sol b/src/Ledger.sol new file mode 100644 index 0000000..62dc5ec --- /dev/null +++ b/src/Ledger.sol @@ -0,0 +1,362 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol"; +import "./dataLayout/LedgerDataLayout.sol"; +import "./interface/ILedger.sol"; +import "./interface/IVaultManager.sol"; +import "./interface/ILedgerCrossChainManager.sol"; +import "./interface/IMarketManager.sol"; +import "./interface/IFeeManager.sol"; +import "./library/Utils.sol"; +import "./library/Signature.sol"; +import "./library/typesHelper/AccountTypeHelper.sol"; +import "./library/typesHelper/AccountTypePositionHelper.sol"; +import "./library/typesHelper/SafeCastHelper.sol"; +import "./interface/ILedgerImplA.sol"; + +/// @title Ledger contract +/// @author Orderly_Rubick +/// @notice Ledger is responsible for saving traders' Account (balance, perpPosition, and other meta) +/// and global state (e.g. futuresUploadBatchId) +/// This contract should only have one in main-chain (e.g. OP orderly L2) +contract Ledger is ILedger, OwnableUpgradeable, LedgerDataLayout { + using AccountTypeHelper for AccountTypes.Account; + using AccountTypePositionHelper for AccountTypes.PerpPosition; + using SafeCastHelper for *; + + // Using Storage as OZ 5.0 does + struct LedgerStorage { + // Because of EIP170 size limit, the implementation should be split to impl contracts + address ledgerImplA; + } + + // keccak256(abi.encode(uint256(keccak256("orderly.Ledger")) - 1)) & ~bytes32(uint256(0xff)) + bytes32 private constant LedgerStorageLocation = 0x220427b0bfdd3e8fe9a4c85265eee2c38bb3f4591655846e819d36b613b63200; + + function _getLedgerStorage() private pure returns (LedgerStorage storage $) { + assembly { + $.slot := LedgerStorageLocation + } + } + + // TODO ledgerImpl1, LedgerImpl2 addresses start here + // usage: `ledgerImpl1.delegatecall(abi.encodeWithSelector(ILedger.accountDeposit.selector, data));` + + /// @notice require operator + modifier onlyOperatorManager() { + if (msg.sender != operatorManagerAddress) revert OnlyOperatorCanCall(); + _; + } + + /// @notice require crossChainManager + modifier onlyCrossChainManager() { + if (msg.sender != crossChainManagerAddress) revert OnlyCrossChainManagerCanCall(); + _; + } + + /// @notice check non-zero address + modifier nonZeroAddress(address _address) { + if (_address == address(0)) revert AddressZero(); + _; + } + + constructor() { + _disableInitializers(); + } + + function initialize() external override initializer { + __Ownable_init(); + } + + /// @notice Set the address of ledgerImplA contract + function setLedgerImplA(address _ledgerImplA) public override onlyOwner nonZeroAddress(_ledgerImplA) { + emit ChangeLedgerImplA(_getLedgerStorage().ledgerImplA, _ledgerImplA); + _getLedgerStorage().ledgerImplA = _ledgerImplA; + } + + /// @notice Set the address of operatorManager contract + /// @param _operatorManagerAddress new operatorManagerAddress + function setOperatorManagerAddress(address _operatorManagerAddress) + public + override + onlyOwner + nonZeroAddress(_operatorManagerAddress) + { + emit ChangeOperatorManager(operatorManagerAddress, _operatorManagerAddress); + operatorManagerAddress = _operatorManagerAddress; + } + + /// @notice Set the address of crossChainManager on Ledger side + /// @param _crossChainManagerAddress new crossChainManagerAddress + function setCrossChainManager(address _crossChainManagerAddress) + public + override + onlyOwner + nonZeroAddress(_crossChainManagerAddress) + { + emit ChangeCrossChainManager(crossChainManagerAddress, _crossChainManagerAddress); + crossChainManagerAddress = _crossChainManagerAddress; + } + + /// @notice Set the address of vaultManager contract + /// @param _vaultManagerAddress new vaultManagerAddress + function setVaultManager(address _vaultManagerAddress) + public + override + onlyOwner + nonZeroAddress(_vaultManagerAddress) + { + emit ChangeVaultManager(address(vaultManager), _vaultManagerAddress); + vaultManager = IVaultManager(_vaultManagerAddress); + } + + /// @notice Set the address of marketManager contract + /// @param _marketManagerAddress new marketManagerAddress + function setMarketManager(address _marketManagerAddress) + public + override + onlyOwner + nonZeroAddress(_marketManagerAddress) + { + emit ChangeMarketManager(address(marketManager), _marketManagerAddress); + marketManager = IMarketManager(_marketManagerAddress); + } + + /// @notice Set the address of feeManager contract + /// @param _feeManagerAddress new feeManagerAddress + function setFeeManager(address _feeManagerAddress) public override onlyOwner nonZeroAddress(_feeManagerAddress) { + emit ChangeFeeManager(address(feeManager), _feeManagerAddress); + feeManager = IFeeManager(_feeManagerAddress); + } + + /// @notice Get the amount of a token frozen balance for a given account and the corresponding withdrawNonce + /// @param accountId accountId to query + /// @param withdrawNonce withdrawNonce to query + /// @param tokenHash tokenHash to query + /// @return uint128 frozen value + function getFrozenWithdrawNonce(bytes32 accountId, uint64 withdrawNonce, bytes32 tokenHash) + public + view + override + returns (uint128) + { + return userLedger[accountId].getFrozenWithdrawNonceBalance(withdrawNonce, tokenHash); + } + + /// @notice omni batch get + /// @param accountIds accountId list to query + /// @param tokens token list to query + /// @param symbols symbol list to query + /// @return accountSnapshots account snapshot list for the given tokens and symbols + function batchGetUserLedger(bytes32[] calldata accountIds, bytes32[] memory tokens, bytes32[] memory symbols) + public + view + override + returns (AccountTypes.AccountSnapshot[] memory accountSnapshots) + { + uint256 accountIdLength = accountIds.length; + uint256 tokenLength = tokens.length; + uint256 symbolLength = symbols.length; + accountSnapshots = new AccountTypes.AccountSnapshot[](accountIdLength); + for (uint256 i = 0; i < accountIdLength; ++i) { + bytes32 accountId = accountIds[i]; + AccountTypes.Account storage account = userLedger[accountId]; + AccountTypes.AccountTokenBalances[] memory tokenInner = new AccountTypes.AccountTokenBalances[](tokenLength); + for (uint256 j = 0; j < tokenLength; ++j) { + bytes32 tokenHash = tokens[j]; + tokenInner[j] = AccountTypes.AccountTokenBalances({ + tokenHash: tokenHash, + balance: account.getBalance(tokenHash), + frozenBalance: account.getFrozenTotalBalance(tokenHash) + }); + } + AccountTypes.AccountPerpPositions[] memory symbolInner = + new AccountTypes.AccountPerpPositions[](symbolLength); + for (uint256 j = 0; j < symbolLength; ++j) { + bytes32 symbolHash = symbols[j]; + AccountTypes.PerpPosition storage perpPosition = account.perpPositions[symbolHash]; + symbolInner[j] = AccountTypes.AccountPerpPositions({ + symbolHash: symbolHash, + positionQty: perpPosition.positionQty, + costPosition: perpPosition.costPosition, + lastSumUnitaryFundings: perpPosition.lastSumUnitaryFundings, + lastExecutedPrice: perpPosition.lastExecutedPrice, + lastSettledPrice: perpPosition.lastSettledPrice, + averageEntryPrice: perpPosition.averageEntryPrice, + openingCost: perpPosition.openingCost, + lastAdlPrice: perpPosition.lastAdlPrice + }); + } + accountSnapshots[i] = AccountTypes.AccountSnapshot({ + accountId: accountId, + brokerHash: account.brokerHash, + userAddress: account.userAddress, + lastWithdrawNonce: account.lastWithdrawNonce, + lastPerpTradeId: account.lastPerpTradeId, + lastEngineEventId: account.lastEngineEventId, + lastDepositEventId: account.lastDepositEventId, + tokenBalances: tokenInner, + perpPositions: symbolInner + }); + } + } + + function batchGetUserLedger(bytes32[] calldata accountIds) + public + view + returns (AccountTypes.AccountSnapshot[] memory) + { + bytes32[] memory tokens = vaultManager.getAllAllowedToken(); + bytes32[] memory symbols = vaultManager.getAllAllowedSymbol(); + return batchGetUserLedger(accountIds, tokens, symbols); + } + + /// Interface implementation + + /// @notice The cross chain manager will call this function to notify the deposit event to the Ledger contract + /// @param data account deposit data + function accountDeposit(AccountTypes.AccountDeposit calldata data) external override onlyCrossChainManager { + _delegatecall(abi.encodeWithSelector(ILedgerImplA.accountDeposit.selector, data)); + } + + function executeProcessValidatedFutures(PerpTypes.FuturesTradeUpload calldata trade) + external + override + onlyOperatorManager + { + _delegatecall(abi.encodeWithSelector(ILedgerImplA.executeProcessValidatedFutures.selector, trade)); + } + + function executeWithdrawAction(EventTypes.WithdrawData calldata withdraw, uint64 eventId) + external + override + onlyOperatorManager + { + _delegatecall(abi.encodeWithSelector(ILedgerImplA.executeWithdrawAction.selector, withdraw, eventId)); + } + + function accountWithDrawFinish(AccountTypes.AccountWithdraw calldata withdraw) + external + override + onlyCrossChainManager + { + _delegatecall(abi.encodeWithSelector(ILedgerImplA.accountWithDrawFinish.selector, withdraw)); + } + + function executeSettlement(EventTypes.Settlement calldata settlement, uint64 eventId) + external + override + onlyOperatorManager + { + _delegatecall(abi.encodeWithSelector(ILedgerImplA.executeSettlement.selector, settlement, eventId)); + } + + function executeLiquidation(EventTypes.Liquidation calldata liquidation, uint64 eventId) + external + override + onlyOperatorManager + { + _delegatecall(abi.encodeWithSelector(ILedgerImplA.executeLiquidation.selector, liquidation, eventId)); + } + + function executeAdl(EventTypes.Adl calldata adl, uint64 eventId) external override onlyOperatorManager { + _delegatecall(abi.encodeWithSelector(ILedgerImplA.executeAdl.selector, adl, eventId)); + } + + function executeFeeDistribution(EventTypes.FeeDistribution calldata feeDistribution, uint64 eventId) + external + override + onlyOperatorManager + { + _delegatecall(abi.encodeWithSelector(ILedgerImplA.executeFeeDistribution.selector, feeDistribution, eventId)); + } + + function executeDelegateSigner(EventTypes.DelegateSigner calldata delegateSigner, uint64 eventId) + external + override + onlyOperatorManager + { + _delegatecall(abi.encodeWithSelector(ILedgerImplA.executeDelegateSigner.selector, delegateSigner, eventId)); + } + + function executeDelegateWithdrawAction(EventTypes.WithdrawData calldata delegateWithdraw, uint64 eventId) + external + override + onlyOperatorManager + { + _delegatecall( + abi.encodeWithSelector(ILedgerImplA.executeDelegateWithdrawAction.selector, delegateWithdraw, eventId) + ); + } + + function executeRebalanceBurn(RebalanceTypes.RebalanceBurnUploadData calldata data) + external + override + onlyOperatorManager + { + (uint32 dstDomain, address dstVaultAddress) = vaultManager.executeRebalanceBurn(data); + // send cc message with: + // rebalanceId, amount, tokenHash, burnChainId, mintChainId | dstDomain, dstVaultAddress + ILedgerCrossChainManager(crossChainManagerAddress).burn( + RebalanceTypes.RebalanceBurnCCData({ + dstDomain: dstDomain, + rebalanceId: data.rebalanceId, + amount: data.amount, + tokenHash: data.tokenHash, + burnChainId: data.burnChainId, + mintChainId: data.mintChainId, + dstVaultAddress: dstVaultAddress + }) + ); + } + + function rebalanceBurnFinish(RebalanceTypes.RebalanceBurnCCFinishData calldata data) + external + override + onlyCrossChainManager + { + vaultManager.rebalanceBurnFinish(data); + } + + function executeRebalanceMint(RebalanceTypes.RebalanceMintUploadData calldata data) + external + override + onlyOperatorManager + { + vaultManager.executeRebalanceMint(data); + // send cc Message with: + // rebalanceId, amount, tokenHash, burnChainId, mintChainId | messageBytes, messageSignature + ILedgerCrossChainManager(crossChainManagerAddress).mint( + RebalanceTypes.RebalanceMintCCData({ + rebalanceId: data.rebalanceId, + amount: data.amount, + tokenHash: data.tokenHash, + burnChainId: data.burnChainId, + mintChainId: data.mintChainId, + messageBytes: data.messageBytes, + messageSignature: data.messageSignature + }) + ); + } + + function rebalanceMintFinish(RebalanceTypes.RebalanceMintCCFinishData calldata data) + external + override + onlyCrossChainManager + { + vaultManager.rebalanceMintFinish(data); + } + + // inner function for delegatecall + function _delegatecall(bytes memory data) private { + (bool success, bytes memory returnData) = _getLedgerStorage().ledgerImplA.delegatecall(data); + if (!success) { + if (returnData.length > 0) { + revert(string(returnData)); + } else { + revert DelegatecallFail(); + } + } + } +} diff --git a/src/LedgerComponent.sol b/src/LedgerComponent.sol new file mode 100644 index 0000000..6e68912 --- /dev/null +++ b/src/LedgerComponent.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "./interface/ILedgerComponent.sol"; +import "openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol"; + +/// @title A component of Ledger +/// @author Orderly_Rubick +/// @notice LedgerComponent is an component which can only be called by ledger (setter) +abstract contract LedgerComponent is ILedgerComponent, OwnableUpgradeable { + // Ledger address + address public ledgerAddress; + + function initialize() external virtual override initializer { + __Ownable_init(); + } + + /// @notice only ledger + modifier onlyLedger() { + if (msg.sender != ledgerAddress) revert OnlyLedgerCanCall(); + _; + } + + /// @notice set ledgerAddress + function setLedgerAddress(address _ledgerAddress) public override onlyOwner { + if (_ledgerAddress == address(0)) revert LedgerAddressZero(); + emit ChangeLedger(ledgerAddress, _ledgerAddress); + ledgerAddress = _ledgerAddress; + } +} diff --git a/src/LedgerImplA.sol b/src/LedgerImplA.sol new file mode 100644 index 0000000..b3f6367 --- /dev/null +++ b/src/LedgerImplA.sol @@ -0,0 +1,611 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol"; +import "./dataLayout/LedgerDataLayout.sol"; +import "./interface/ILedger.sol"; +import "./interface/IVaultManager.sol"; +import "./interface/ILedgerCrossChainManager.sol"; +import "./interface/IMarketManager.sol"; +import "./interface/IFeeManager.sol"; +import "./interface/ILedgerImplA.sol"; +import "./library/Utils.sol"; +import "./library/Signature.sol"; +import "./library/typesHelper/AccountTypeHelper.sol"; +import "./library/typesHelper/AccountTypePositionHelper.sol"; +import "./library/typesHelper/SafeCastHelper.sol"; + +/// @title Ledger contract, implementation part A contract, for resolve EIP170 limit +/// @author Orderly_Rubick +contract LedgerImplA is ILedgerImplA, OwnableUpgradeable, LedgerDataLayout { + using AccountTypeHelper for AccountTypes.Account; + using AccountTypePositionHelper for AccountTypes.PerpPosition; + using SafeCastHelper for *; + + constructor() { + _disableInitializers(); + } + + function initialize() external override initializer { + __Ownable_init(); + } + + /// Interface implementation + + /// @notice The cross chain manager will call this function to notify the deposit event to the Ledger contract + /// @param data account deposit data + function accountDeposit(AccountTypes.AccountDeposit calldata data) external override { + // validate data first + if (!vaultManager.getAllowedBroker(data.brokerHash)) revert BrokerNotAllowed(); + if (!vaultManager.getAllowedChainToken(data.tokenHash, data.srcChainId)) { + revert TokenNotAllowed(data.tokenHash, data.srcChainId); + } + if (!Utils.validateAccountId(data.accountId, data.brokerHash, data.userAddress)) revert AccountIdInvalid(); + + // a not registerd account can still deposit, because of the consistency + AccountTypes.Account storage account = userLedger[data.accountId]; + if (account.userAddress == address(0)) { + // register account first + account.userAddress = data.userAddress; + account.brokerHash = data.brokerHash; + // emit register event + emit AccountRegister(data.accountId, data.brokerHash, data.userAddress); + } + account.addBalance(data.tokenHash, data.tokenAmount); + vaultManager.addBalance(data.tokenHash, data.srcChainId, data.tokenAmount); + uint64 tmpGlobalEventId = _newGlobalEventId(); // gas saving + account.lastDepositEventId = tmpGlobalEventId; + // emit deposit event + emit AccountDeposit( + data.accountId, + _newGlobalDepositId(), + tmpGlobalEventId, + data.userAddress, + data.tokenHash, + data.tokenAmount, + data.srcChainId, + data.srcChainDepositNonce, + data.brokerHash + ); + } + + function executeProcessValidatedFutures(PerpTypes.FuturesTradeUpload calldata trade) external override { + // validate data first + if (!vaultManager.getAllowedSymbol(trade.symbolHash)) revert SymbolNotAllowed(); + // do the logic part + AccountTypes.Account storage account = userLedger[trade.accountId]; + AccountTypes.PerpPosition storage perpPosition = account.perpPositions[trade.symbolHash]; + perpPosition.chargeFundingFee(trade.sumUnitaryFundings); + perpPosition.calAverageEntryPrice(trade.tradeQty, trade.executedPrice.toInt128(), 0); + perpPosition.positionQty += trade.tradeQty; + perpPosition.costPosition += trade.notional; + perpPosition.lastExecutedPrice = trade.executedPrice; + // fee_swap_position + _feeSwapPosition(perpPosition, trade.symbolHash, trade.fee, trade.tradeId, trade.sumUnitaryFundings); + account.lastPerpTradeId = trade.tradeId; + // update last funding update timestamp + marketManager.setLastFundingUpdated(trade.symbolHash, trade.timestamp); + // emit event + emit ProcessValidatedFutures( + trade.accountId, + trade.symbolHash, + trade.feeAssetHash, + trade.tradeQty, + trade.notional, + trade.executedPrice, + trade.fee, + trade.sumUnitaryFundings, + trade.tradeId, + trade.matchId, + trade.timestamp, + trade.side + ); + } + + function executeWithdrawAction(EventTypes.WithdrawData calldata withdraw, uint64 eventId) external override { + bytes32 brokerHash = Utils.calculateStringHash(withdraw.brokerId); + bytes32 tokenHash = Utils.calculateStringHash(withdraw.tokenSymbol); + if (!vaultManager.getAllowedBroker(brokerHash)) revert BrokerNotAllowed(); + if (!vaultManager.getAllowedChainToken(tokenHash, withdraw.chainId)) { + revert TokenNotAllowed(tokenHash, withdraw.chainId); + } + if (!Utils.validateAccountId(withdraw.accountId, brokerHash, withdraw.sender)) revert AccountIdInvalid(); + AccountTypes.Account storage account = userLedger[withdraw.accountId]; + uint8 state = 0; + { + // avoid stack too deep + uint128 maxWithdrawFee = vaultManager.getMaxWithdrawFee(tokenHash); + // https://wootraders.atlassian.net/wiki/spaces/ORDER/pages/326402549/Withdraw+Error+Code + if (account.lastWithdrawNonce >= withdraw.withdrawNonce) { + // require withdraw nonce inc + state = 101; + } else if (account.balances[tokenHash] < withdraw.tokenAmount) { + // require balance enough + state = 1; + } else if (vaultManager.getBalance(tokenHash, withdraw.chainId) < withdraw.tokenAmount - withdraw.fee) { + // require chain has enough balance + state = 2; + } else if (!Signature.verifyWithdraw(withdraw.sender, withdraw)) { + // require signature verify + state = 4; + } else if (maxWithdrawFee > 0 && maxWithdrawFee < withdraw.fee) { + // require fee not exceed maxWithdrawFee + state = 5; + } else if (withdraw.receiver == address(0)) { + // require receiver not zero address + state = 6; + } + } + // check all assert, should not change any status + if (state != 0) { + emit AccountWithdrawFail( + withdraw.accountId, + withdraw.withdrawNonce, + _newGlobalEventId(), + brokerHash, + withdraw.sender, + withdraw.receiver, + withdraw.chainId, + tokenHash, + withdraw.tokenAmount, + withdraw.fee, + state + ); + return; + } + // update status, should never fail + // frozen balance + // account should frozen `tokenAmount`, and vault should frozen `tokenAmount - fee`, because vault will payout `tokenAmount - fee` + account.frozenBalance(withdraw.withdrawNonce, tokenHash, withdraw.tokenAmount); + vaultManager.frozenBalance(tokenHash, withdraw.chainId, withdraw.tokenAmount - withdraw.fee); + account.lastEngineEventId = eventId; + // emit withdraw approve event + emit AccountWithdrawApprove( + withdraw.accountId, + withdraw.withdrawNonce, + _newGlobalEventId(), + brokerHash, + withdraw.sender, + withdraw.receiver, + withdraw.chainId, + tokenHash, + withdraw.tokenAmount, + withdraw.fee + ); + // send cross-chain tx + ILedgerCrossChainManager(crossChainManagerAddress).withdraw(withdraw); + } + + function accountWithDrawFinish(AccountTypes.AccountWithdraw calldata withdraw) external override { + AccountTypes.Account storage account = userLedger[withdraw.accountId]; + // finish frozen balance + account.finishFrozenBalance(withdraw.withdrawNonce, withdraw.tokenHash, withdraw.tokenAmount); + vaultManager.finishFrozenBalance(withdraw.tokenHash, withdraw.chainId, withdraw.tokenAmount - withdraw.fee); + // withdraw fee + if (withdraw.fee > 0) { + // gas saving if no fee + bytes32 feeCollectorAccountId = + feeManager.getFeeCollector(IFeeManager.FeeCollectorType.WithdrawFeeCollector); + AccountTypes.Account storage feeCollectorAccount = userLedger[feeCollectorAccountId]; + feeCollectorAccount.addBalance(withdraw.tokenHash, withdraw.fee); + } + // emit withdraw finish event + emit AccountWithdrawFinish( + withdraw.accountId, + withdraw.withdrawNonce, + _newGlobalEventId(), + withdraw.brokerHash, + withdraw.sender, + withdraw.receiver, + withdraw.chainId, + withdraw.tokenHash, + withdraw.tokenAmount, + withdraw.fee + ); + } + + function executeSettlement(EventTypes.Settlement calldata settlement, uint64 eventId) external override { + // check total settle amount zero + int128 totalSettleAmount = 0; + // gas saving + uint256 length = settlement.settlementExecutions.length; + EventTypes.SettlementExecution[] calldata settlementExecutions = settlement.settlementExecutions; + AccountTypes.Account storage account = userLedger[settlement.accountId]; + if (settlement.insuranceTransferAmount != 0) { + if (settlement.accountId == settlement.insuranceAccountId) revert InsuranceTransferToSelf(); + uint128 balance = account.balances[settlement.settledAssetHash]; + // transfer insurance fund + if ( + balance.toInt128() + settlement.insuranceTransferAmount.toInt128() + settlement.settledAmount < 0 + || settlement.insuranceTransferAmount > settlement.settledAmount.abs() + ) { + // overflow + revert InsuranceTransferAmountInvalid( + balance, settlement.insuranceTransferAmount, settlement.settledAmount + ); + } + AccountTypes.Account storage insuranceFund = userLedger[settlement.insuranceAccountId]; + insuranceFund.subBalance(settlement.settledAssetHash, settlement.insuranceTransferAmount); + account.addBalance(settlement.settledAssetHash, settlement.insuranceTransferAmount); + } + // for-loop ledger execution + for (uint256 i = 0; i < length; ++i) { + EventTypes.SettlementExecution calldata ledgerExecution = settlementExecutions[i]; + totalSettleAmount += ledgerExecution.settledAmount; + if (!vaultManager.getAllowedSymbol(ledgerExecution.symbolHash)) revert SymbolNotAllowed(); + AccountTypes.PerpPosition storage position = account.perpPositions[ledgerExecution.symbolHash]; + position.chargeFundingFee(ledgerExecution.sumUnitaryFundings); + position.costPosition += ledgerExecution.settledAmount; + position.lastExecutedPrice = ledgerExecution.markPrice; + position.lastSettledPrice = ledgerExecution.markPrice; + // check balance + settledAmount >= 0, where balance should cast to int128 first + uint128 balance = account.balances[settlement.settledAssetHash]; + if (balance.toInt128() + ledgerExecution.settledAmount < 0) { + revert BalanceNotEnough(balance, ledgerExecution.settledAmount); + } + account.balances[settlement.settledAssetHash] = + (balance.toInt128() + ledgerExecution.settledAmount).toUint128(); + if (position.isFullSettled()) { + delete account.perpPositions[ledgerExecution.symbolHash]; + } + emit SettlementExecution( + ledgerExecution.symbolHash, + ledgerExecution.markPrice, + ledgerExecution.sumUnitaryFundings, + ledgerExecution.settledAmount + ); + } + if (totalSettleAmount != settlement.settledAmount) revert TotalSettleAmountNotMatch(totalSettleAmount); + account.lastEngineEventId = eventId; + // emit event + emit SettlementResult( + _newGlobalEventId(), + settlement.accountId, + settlement.settledAmount, + settlement.settledAssetHash, + settlement.insuranceAccountId, + settlement.insuranceTransferAmount, + uint64(length), + eventId + ); + } + + function executeLiquidation(EventTypes.Liquidation calldata liquidation, uint64 eventId) external override { + AccountTypes.Account storage liquidatedAccount = userLedger[liquidation.liquidatedAccountId]; + + if (liquidation.insuranceTransferAmount != 0) { + _transferLiquidatedAssetToInsurance( + liquidatedAccount, + liquidation.liquidatedAssetHash, + liquidation.insuranceTransferAmount, + liquidation.insuranceAccountId + ); + } + uint256 length = liquidation.liquidationTransfers.length; + for (uint256 i = 0; i < length; i++) { + EventTypes.LiquidationTransfer calldata liquidationTransfer = liquidation.liquidationTransfers[i]; + _liquidatorLiquidateAndUpdateEventId( + liquidationTransfer, eventId, liquidationTransfer.liquidatorAccountId != liquidation.insuranceAccountId + ); + _liquidatedAccountLiquidate( + liquidatedAccount, + liquidationTransfer, + liquidation.liquidatedAccountId != liquidation.insuranceAccountId + ); + _insuranceLiquidateAndUpdateEventId(liquidation.insuranceAccountId, liquidationTransfer, eventId); + emit LiquidationTransfer( + liquidationTransfer.liquidationTransferId, + liquidationTransfer.liquidatorAccountId, + liquidationTransfer.symbolHash, + liquidationTransfer.positionQtyTransfer, + liquidationTransfer.costPositionTransfer, + liquidationTransfer.liquidatorFee, + liquidationTransfer.insuranceFee, + liquidationTransfer.liquidationFee, + liquidationTransfer.markPrice, + liquidationTransfer.sumUnitaryFundings + ); + } + liquidatedAccount.lastEngineEventId = eventId; + // emit event + emit LiquidationResult( + _newGlobalEventId(), + liquidation.liquidatedAccountId, + liquidation.insuranceAccountId, + liquidation.liquidatedAssetHash, + liquidation.insuranceTransferAmount, + eventId + ); + } + + function executeAdl(EventTypes.Adl calldata adl, uint64 eventId) external override { + if (!vaultManager.getAllowedSymbol(adl.symbolHash)) revert SymbolNotAllowed(); + AccountTypes.Account storage account = userLedger[adl.accountId]; + AccountTypes.PerpPosition storage userPosition = account.perpPositions[adl.symbolHash]; + userPosition.chargeFundingFee(adl.sumUnitaryFundings); + AccountTypes.Account storage insuranceFund = userLedger[adl.insuranceAccountId]; + AccountTypes.PerpPosition storage insurancePosition = insuranceFund.perpPositions[adl.symbolHash]; + int128 tmpUserPositionQty = userPosition.positionQty; // gas saving + if (tmpUserPositionQty == 0) revert UserPerpPositionQtyZero(adl.accountId, adl.symbolHash); + if (adl.positionQtyTransfer.abs() > tmpUserPositionQty.abs()) { + revert InsurancePositionQtyInvalid(adl.positionQtyTransfer, tmpUserPositionQty); + } + + insurancePosition.chargeFundingFee(adl.sumUnitaryFundings); + insurancePosition.positionQty -= adl.positionQtyTransfer; + userPosition.calAverageEntryPrice(adl.positionQtyTransfer, adl.adlPrice.toInt128(), -adl.costPositionTransfer); + userPosition.positionQty += adl.positionQtyTransfer; + insurancePosition.costPosition -= adl.costPositionTransfer; + userPosition.costPosition += adl.costPositionTransfer; + + userPosition.lastExecutedPrice = adl.adlPrice; + userPosition.lastAdlPrice = adl.adlPrice; + + insurancePosition.lastExecutedPrice = adl.adlPrice; + insurancePosition.lastAdlPrice = adl.adlPrice; + + account.lastEngineEventId = eventId; + insuranceFund.lastEngineEventId = eventId; + emit AdlResult( + _newGlobalEventId(), + adl.accountId, + adl.insuranceAccountId, + adl.symbolHash, + adl.positionQtyTransfer, + adl.costPositionTransfer, + adl.adlPrice, + adl.sumUnitaryFundings, + eventId + ); + } + + function executeFeeDistribution(EventTypes.FeeDistribution calldata feeDistribution, uint64 eventId) + external + override + { + AccountTypes.Account storage fromAccount = userLedger[feeDistribution.fromAccountId]; + AccountTypes.Account storage toAccount = userLedger[feeDistribution.toAccountId]; + fromAccount.subBalance(feeDistribution.tokenHash, feeDistribution.amount); + toAccount.addBalance(feeDistribution.tokenHash, feeDistribution.amount); + fromAccount.lastEngineEventId = eventId; + toAccount.lastEngineEventId = eventId; + // emit event + emit FeeDistribution( + _newGlobalEventId(), + feeDistribution.fromAccountId, + feeDistribution.toAccountId, + feeDistribution.amount, + feeDistribution.tokenHash + ); + } + + function executeDelegateSigner(EventTypes.DelegateSigner calldata delegateSigner, uint64 eventId) + external + override + { + // check if cefi has uploaded wrong info + if (!vaultManager.getAllowedBroker(delegateSigner.brokerHash)) revert BrokerNotAllowed(); + if (delegateSigner.delegateContract == address(0)) revert ZeroDelegateContract(); + if (delegateSigner.delegateSigner == address(0)) revert ZeroDelegateSigner(); + if (delegateSigner.chainId == 0) revert ZeroChainId(); + + bytes32 accountId = Utils.calculateAccountId(delegateSigner.delegateContract, delegateSigner.brokerHash); + + // only support one chain delegation + if (contractSigner[accountId].chainId != 0 && contractSigner[accountId].chainId != delegateSigner.chainId) { + revert DelegateChainIdNotMatch(accountId, contractSigner[accountId].chainId, delegateSigner.chainId); + } + AccountTypes.AccountDelegateSigner memory accountDelegateSigner = + AccountTypes.AccountDelegateSigner({chainId: delegateSigner.chainId, signer: delegateSigner.delegateSigner}); + + contractSigner[accountId] = accountDelegateSigner; + AccountTypes.Account storage account = userLedger[accountId]; + account.lastEngineEventId = eventId; + emit DelegateSigner( + _newGlobalEventId(), + delegateSigner.chainId, + accountId, + delegateSigner.delegateContract, + delegateSigner.brokerHash, + delegateSigner.delegateSigner + ); + } + + function executeDelegateWithdrawAction(EventTypes.WithdrawData calldata withdraw, uint64 eventId) + external + override + { + // withdraw check + bytes32 brokerHash = Utils.calculateStringHash(withdraw.brokerId); + bytes32 tokenHash = Utils.calculateStringHash(withdraw.tokenSymbol); + if (!vaultManager.getAllowedBroker(brokerHash)) revert BrokerNotAllowed(); + if (!vaultManager.getAllowedChainToken(tokenHash, withdraw.chainId)) { + revert TokenNotAllowed(tokenHash, withdraw.chainId); + } + if (!Utils.validateAccountId(withdraw.accountId, brokerHash, withdraw.sender)) { + revert AccountIdInvalid(); + } + AccountTypes.Account storage account = userLedger[withdraw.accountId]; + uint8 state = 0; + { + // avoid stack too deep + AccountTypes.AccountDelegateSigner storage accountDelegateSigner = contractSigner[withdraw.accountId]; + uint128 maxWithdrawFee = vaultManager.getMaxWithdrawFee(tokenHash); + // https://wootraders.atlassian.net/wiki/spaces/ORDER/pages/326402549/Withdraw+Error+Code + if (account.lastWithdrawNonce >= withdraw.withdrawNonce) { + // require withdraw nonce inc + state = 101; + } else if (account.balances[tokenHash] < withdraw.tokenAmount) { + // require balance enough + state = 1; + } else if (vaultManager.getBalance(tokenHash, withdraw.chainId) < withdraw.tokenAmount - withdraw.fee) { + // require chain has enough balance + state = 2; + } else if (!Signature.verifyDelegateWithdraw(accountDelegateSigner.signer, withdraw)) { + // require signature verify + state = 4; + } else if (maxWithdrawFee > 0 && maxWithdrawFee < withdraw.fee) { + // require fee not exceed maxWithdrawFee + state = 5; + } else if (withdraw.receiver == address(0)) { + // require receiver not zero address + state = 6; + } else if (accountDelegateSigner.chainId != withdraw.chainId) { + // require chainId match + state = 7; + } else if (withdraw.receiver != withdraw.sender) { + // require sender = receiver + state = 8; + } + } + // check all assert, should not change any status + + if (state != 0) { + emit AccountWithdrawFail( + withdraw.accountId, + withdraw.withdrawNonce, + _newGlobalEventId(), + brokerHash, + withdraw.sender, + withdraw.receiver, + withdraw.chainId, + tokenHash, + withdraw.tokenAmount, + withdraw.fee, + state + ); + return; + } + // update status, should never fail + // frozen balance + // account should frozen `tokenAmount`, and vault should frozen `tokenAmount - fee`, because vault will payout `tokenAmount - fee` + account.frozenBalance(withdraw.withdrawNonce, tokenHash, withdraw.tokenAmount); + vaultManager.frozenBalance(tokenHash, withdraw.chainId, withdraw.tokenAmount - withdraw.fee); + account.lastEngineEventId = eventId; + // emit withdraw approve event + + emit AccountWithdrawApprove( + withdraw.accountId, + withdraw.withdrawNonce, + _newGlobalEventId(), + brokerHash, + withdraw.sender, + withdraw.receiver, + withdraw.chainId, + tokenHash, + withdraw.tokenAmount, + withdraw.fee + ); + // send cross-chain tx + ILedgerCrossChainManager(crossChainManagerAddress).withdraw(withdraw); + } + + function _newGlobalEventId() internal returns (uint64) { + return ++globalEventId; + } + + function _newGlobalDepositId() internal returns (uint64) { + return ++globalDepositId; + } + + // =================== internal =================== // + + function _feeSwapPosition( + AccountTypes.PerpPosition storage traderPosition, + bytes32 symbol, + int128 feeAmount, + uint64 tradeId, + int128 sumUnitaryFundings + ) internal { + if (feeAmount == 0) return; + _perpFeeCollectorDeposit(symbol, feeAmount, tradeId, sumUnitaryFundings); + traderPosition.costPosition += feeAmount; + } + + function _perpFeeCollectorDeposit(bytes32 symbol, int128 amount, uint64 tradeId, int128 sumUnitaryFundings) + internal + { + bytes32 feeCollectorAccountId = feeManager.getFeeCollector(IFeeManager.FeeCollectorType.FuturesFeeCollector); + AccountTypes.Account storage feeCollectorAccount = userLedger[feeCollectorAccountId]; + AccountTypes.PerpPosition storage feeCollectorPosition = feeCollectorAccount.perpPositions[symbol]; + feeCollectorPosition.costPosition -= amount; + feeCollectorPosition.lastSumUnitaryFundings = sumUnitaryFundings; + if (tradeId > feeCollectorAccount.lastPerpTradeId) { + feeCollectorAccount.lastPerpTradeId = tradeId; + } + } + + // =================== liquidation functions =================== // + + function _transferLiquidatedAssetToInsurance( + AccountTypes.Account storage liquidatedAccount, + bytes32 liquidatedAssetHash, + uint128 insuranceTransferAmount, + bytes32 insuranceAccountId + ) internal { + liquidatedAccount.subBalance(liquidatedAssetHash, insuranceTransferAmount); + AccountTypes.Account storage insuranceFund = userLedger[insuranceAccountId]; + insuranceFund.addBalance(liquidatedAssetHash, insuranceTransferAmount); + } + + function _liquidatorLiquidateAndUpdateEventId( + EventTypes.LiquidationTransfer calldata liquidationTransfer, + uint64 eventId, + bool needCalAvg + ) internal { + AccountTypes.Account storage liquidatorAccount = userLedger[liquidationTransfer.liquidatorAccountId]; + AccountTypes.PerpPosition storage liquidatorPosition = + liquidatorAccount.perpPositions[liquidationTransfer.symbolHash]; + liquidatorPosition.chargeFundingFee(liquidationTransfer.sumUnitaryFundings); + if (needCalAvg) { + liquidatorPosition.calAverageEntryPrice( + liquidationTransfer.positionQtyTransfer, + liquidationTransfer.markPrice.toInt128(), + -(liquidationTransfer.costPositionTransfer - liquidationTransfer.liquidatorFee) + ); + } + liquidatorPosition.positionQty += liquidationTransfer.positionQtyTransfer; + liquidatorPosition.costPosition += liquidationTransfer.costPositionTransfer - liquidationTransfer.liquidatorFee; + liquidatorPosition.lastExecutedPrice = liquidationTransfer.markPrice; + liquidatorAccount.lastEngineEventId = eventId; + } + + function _liquidatedAccountLiquidate( + AccountTypes.Account storage liquidatedAccount, + EventTypes.LiquidationTransfer calldata liquidationTransfer, + bool needCalAvg + ) internal { + AccountTypes.PerpPosition storage liquidatedPosition = + liquidatedAccount.perpPositions[liquidationTransfer.symbolHash]; + liquidatedPosition.chargeFundingFee(liquidationTransfer.sumUnitaryFundings); + if (needCalAvg) { + liquidatedPosition.calAverageEntryPrice( + -liquidationTransfer.positionQtyTransfer, + liquidationTransfer.markPrice.toInt128(), + liquidationTransfer.costPositionTransfer + - (liquidationTransfer.liquidatorFee + liquidationTransfer.insuranceFee) + ); + } + liquidatedPosition.positionQty -= liquidationTransfer.positionQtyTransfer; + + liquidatedPosition.costPosition += liquidationTransfer.liquidationFee - liquidationTransfer.costPositionTransfer; + liquidatedPosition.lastExecutedPrice = liquidationTransfer.markPrice; + if (liquidatedPosition.isFullSettled()) { + delete liquidatedAccount.perpPositions[liquidationTransfer.symbolHash]; + } + } + + function _insuranceLiquidateAndUpdateEventId( + bytes32 insuranceAccountId, + EventTypes.LiquidationTransfer calldata liquidationTransfer, + uint64 eventId + ) internal { + AccountTypes.Account storage insuranceFund = userLedger[insuranceAccountId]; + AccountTypes.PerpPosition storage insurancePosition = + insuranceFund.perpPositions[liquidationTransfer.symbolHash]; + insurancePosition.chargeFundingFee(liquidationTransfer.sumUnitaryFundings); + insurancePosition.costPosition -= liquidationTransfer.insuranceFee; + insurancePosition.lastExecutedPrice = liquidationTransfer.markPrice; + insuranceFund.lastEngineEventId = eventId; + } +} diff --git a/src/MarketManager.sol b/src/MarketManager.sol new file mode 100644 index 0000000..6d412ae --- /dev/null +++ b/src/MarketManager.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "./interface/IMarketManager.sol"; +import "./library/typesHelper/MarketTypeHelper.sol"; +import "./LedgerComponent.sol"; +import "./OperatorManagerComponent.sol"; + +/// @title A component of Ledger, saves market data +/// @author Orderly_Rubick +/// @notice MarketManager saves perpMarketCfg +contract MarketManager is IMarketManager, LedgerComponent, OperatorManagerComponent { + using MarketTypeHelper for MarketTypes.PerpMarketCfg; + + // pairSymbol => PerpMarketCfg + mapping(bytes32 => MarketTypes.PerpMarketCfg) public perpMarketCfg; + + constructor() { + _disableInitializers(); + } + + function initialize() external override(IMarketManager, LedgerComponent, OperatorManagerComponent) initializer { + __Ownable_init(); + } + + function updateMarketUpload(MarketTypes.UploadPerpPrice calldata data) external onlyOperatorManager { + uint256 length = data.perpPrices.length; + for (uint256 i = 0; i < length; i++) { + MarketTypes.PerpPrice calldata perpPrice = data.perpPrices[i]; + MarketTypes.PerpMarketCfg storage cfg = perpMarketCfg[perpPrice.symbolHash]; + cfg.setIndexPriceOrderly(perpPrice.indexPrice); + cfg.setMarkPrice(perpPrice.markPrice); + cfg.setLastMarkPriceUpdated(perpPrice.timestamp); + } + emit MarketData(data.maxTimestamp); + } + + function updateMarketUpload(MarketTypes.UploadSumUnitaryFundings calldata data) external onlyOperatorManager { + uint256 length = data.sumUnitaryFundings.length; + for (uint256 i = 0; i < length; i++) { + MarketTypes.SumUnitaryFunding calldata sumUnitaryFunding = data.sumUnitaryFundings[i]; + MarketTypes.PerpMarketCfg storage cfg = perpMarketCfg[sumUnitaryFunding.symbolHash]; + cfg.setSumUnitaryFundings(sumUnitaryFunding.sumUnitaryFunding); + cfg.setLastMarkPriceUpdated(sumUnitaryFunding.timestamp); + } + emit FundingData(data.maxTimestamp); + } + + function setPerpMarketCfg(bytes32 _symbolHash, MarketTypes.PerpMarketCfg memory _perpMarketCfg) + external + override + onlyOwner + { + perpMarketCfg[_symbolHash] = _perpMarketCfg; + } + + function getPerpMarketCfg(bytes32 _pairSymbol) public view override returns (MarketTypes.PerpMarketCfg memory) { + return perpMarketCfg[_pairSymbol]; + } + + function setLastMarkPriceUpdated(bytes32 _pairSymbol, uint64 _lastMarkPriceUpdated) external override onlyOwner { + perpMarketCfg[_pairSymbol].setLastMarkPriceUpdated(_lastMarkPriceUpdated); + } + + function setLastFundingUpdated(bytes32 _pairSymbol, uint64 _lastFundingUpdated) external override onlyLedger { + perpMarketCfg[_pairSymbol].setLastFundingUpdated(_lastFundingUpdated); + } +} diff --git a/src/OperatorManager.sol b/src/OperatorManager.sol index ad676f7..830e670 100644 --- a/src/OperatorManager.sol +++ b/src/OperatorManager.sol @@ -1,162 +1,304 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.18; -import "./interface/ISettlement.sol"; +import "openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol"; +import "./dataLayout/OperatorManagerDataLayout.sol"; +import "./interface/ILedger.sol"; +import "./interface/IMarketManager.sol"; import "./interface/IOperatorManager.sol"; -import "openzeppelin-contracts/contracts/access/Ownable.sol"; - -/** - * OperatorManager is responsible for executing cefi tx, only called by operator. - * This contract should only have one in main-chain (avalanche) - */ -contract OperatorManager is IOperatorManager, Ownable { - // operator address - address public operator; - // settlement Interface - ISettlement public settlement; - - // ids - // futuresUploadBatchId - uint256 public futuresUploadBatchId; - // eventUploadBatchId - uint256 public eventUploadBatchId; - // last operator interaction timestamp - uint256 public lastOperatorInteraction; - - // only operator +import "./library/Signature.sol"; + +/// @title Operator call this manager for update data +/// @author Orderly_Rubick +/// @notice OperatorManager is responsible for executing engine tx, only called by operator. +/// @notice This contract should only have one in main-chain +contract OperatorManager is IOperatorManager, OwnableUpgradeable, OperatorManagerDataLayout { + /// @notice Require only operator can call modifier onlyOperator() { - require(msg.sender == operator, "only operator can call"); + // Update: operatorManagerZipAddress is also allowed to call + if (msg.sender != operatorAddress && msg.sender != operatorManagerZipAddress) revert OnlyOperatorCanCall(); _; } - // set operator - function setOperator(address _operator) public onlyOwner { - operator = _operator; + /// @notice check non-zero address + modifier nonZeroAddress(address _address) { + if (_address == address(0)) revert AddressZero(); + _; } - // set settlement - function setSettlement(address _settlement) public onlyOwner { - settlement = ISettlement(_settlement); + /// @notice Set the operator address + function setOperator(address _operatorAddress) public override onlyOwner nonZeroAddress(_operatorAddress) { + emit ChangeOperator(1, operatorAddress, _operatorAddress); + operatorAddress = _operatorAddress; } - // operator ping - function operatorPing() public onlyOperator { - _innerPing(); + /// @notice Set engine signature address for spot trade upload + function setEngineSpotTradeUploadAddress(address _engineSpotTradeUploadAddress) + public + override + onlyOwner + nonZeroAddress(_engineSpotTradeUploadAddress) + { + emit ChangeEngineUpload(1, engineSpotTradeUploadAddress, _engineSpotTradeUploadAddress); + engineSpotTradeUploadAddress = _engineSpotTradeUploadAddress; } - // entry point for operator to call this contract - function operatorExecuteAction(OperatorTypes.OperatorActionData actionData, bytes calldata action) + /// @notice Set engine signature address for perpetual future trade upload + function setEnginePerpTradeUploadAddress(address _enginePerpTradeUploadAddress) public override - onlyOperator + onlyOwner + nonZeroAddress(_enginePerpTradeUploadAddress) + { + emit ChangeEngineUpload(2, enginePerpTradeUploadAddress, _enginePerpTradeUploadAddress); + enginePerpTradeUploadAddress = _enginePerpTradeUploadAddress; + } + + /// @notice Set engine signature address for event upload + function setEngineEventUploadAddress(address _engineEventUploadAddress) + public + override + onlyOwner + nonZeroAddress(_engineEventUploadAddress) + { + emit ChangeEngineUpload(3, engineEventUploadAddress, _engineEventUploadAddress); + engineEventUploadAddress = _engineEventUploadAddress; + } + + /// @notice Set engine signature address for market information upload + function setEngineMarketUploadAddress(address _engineMarketUploadAddress) + public + override + onlyOwner + nonZeroAddress(_engineMarketUploadAddress) { + emit ChangeEngineUpload(4, engineMarketUploadAddress, _engineMarketUploadAddress); + engineMarketUploadAddress = _engineMarketUploadAddress; + } + + /// @notice Set engine signature address for rebalance upload + function setEngineRebalanceUploadAddress(address _engineRebalanceUploadAddress) + public + override + onlyOwner + nonZeroAddress(_engineRebalanceUploadAddress) + { + emit ChangeEngineUpload(5, engineRebalanceUploadAddress, _engineRebalanceUploadAddress); + engineRebalanceUploadAddress = _engineRebalanceUploadAddress; + } + + /// @notice Set the address of OperatorManagerZip contract + function setOperatorManagerZipAddress(address _operatorManagerZipAddress) + public + override + onlyOwner + nonZeroAddress(_operatorManagerZipAddress) + { + emit ChangeOperator(2, operatorManagerZipAddress, _operatorManagerZipAddress); + operatorManagerZipAddress = _operatorManagerZipAddress; + } + + /// @notice Set the address of ledger contract + function setLedger(address _ledger) public override onlyOwner nonZeroAddress(_ledger) { + emit ChangeLedger(address(ledger), _ledger); + ledger = ILedger(_ledger); + } + + /// @notice Set the address of market manager contract + function setMarketManager(address _marketManagerAddress) + public + override + onlyOwner + nonZeroAddress(_marketManagerAddress) + { + emit ChangeMarketManager(address(marketManager), _marketManagerAddress); + marketManager = IMarketManager(_marketManagerAddress); + } + + constructor() { + _disableInitializers(); + } + + function initialize() external override initializer { + __Ownable_init(); + futuresUploadBatchId = 1; + eventUploadBatchId = 1; + lastOperatorInteraction = block.timestamp; + // init all engine sign address + // https://wootraders.atlassian.net/wiki/spaces/ORDER/pages/315785217/Orderly+V2+Keys+Smart+Contract + engineSpotTradeUploadAddress = 0x4348C254D611fe55Ff1c58e7E20D33C14B0021A1; + enginePerpTradeUploadAddress = 0xd642D1b669b5021C057A81A917E1e831D97484AA; + engineEventUploadAddress = 0x1a4C6008d576E32b0B3D84E7620B4f4623c0cB38; + engineMarketUploadAddress = 0xE238F0A623D405b943B76700F85C56f0BE7d38da; + operatorAddress = 0x056e1e2bF9F5C856A5D115e2B04742AE877098ac; + } + + /// @notice Operator ping to update last operator interaction timestamp + function operatorPing() public onlyOperator { _innerPing(); - if (actionData == OperatorTypes.OperatorActionData.FuturesTradeUpload) { - // FuturesTradeUpload - futuresTradeUploadData(abi.decode(action, (PerpTypes.FuturesTradeUploadData))); - } else if (actionData == OperatorTypes.OperatorActionData.EventUpload) { - // EventUpload - eventUploadData(abi.decode(action, (PerpTypes.EventUpload))); - } else if (actionData == OperatorTypes.OperatorActionData.UserRegister) { - // UserRegister - settlement.accountRegister(abi.decode(action, (AccountTypes.AccountRegister))); - } else { - revert("invalid action data"); - } } - // accountRegisterAction - function accountRegisterAction(AccountTypes.AccountRegister calldata data) public override onlyOperator { + /// @notice Function for perpetual futures trade upload + function futuresTradeUpload(PerpTypes.FuturesTradeUploadData calldata data) public override onlyOperator { + if (data.batchId != futuresUploadBatchId) revert BatchIdNotMatch(data.batchId, futuresUploadBatchId); _innerPing(); - settlement.accountRegister(data); + _futuresTradeUploadData(data); + // emit event + emit FuturesTradeUpload(data.batchId); + // next wanted futuresUploadBatchId + futuresUploadBatchId += 1; + } + + /// @notice Function for event upload + function eventUpload(EventTypes.EventUpload calldata data) public override onlyOperator { + if (data.batchId != eventUploadBatchId) revert BatchIdNotMatch(data.batchId, eventUploadBatchId); + _innerPing(); + _eventUploadData(data); + // emit event + emit EventUpload(data.batchId); + // next wanted eventUploadBatchId + eventUploadBatchId += 1; } - // futuresTradeUploadDataAction - function futuresTradeUploadDataAction(PerpTypes.FuturesTradeUploadData calldata data) + /// @notice Function for perpetual futures price upload + function perpPriceUpload(MarketTypes.UploadPerpPrice calldata data) public override onlyOperator { + _innerPing(); + _perpMarketInfo(data); + } + + /// @notice Function for sum unitary fundings upload + function sumUnitaryFundingsUpload(MarketTypes.UploadSumUnitaryFundings calldata data) public override onlyOperator { _innerPing(); - futuresTradeUploadData(data); + _perpMarketInfo(data); + } + + // @notice Function for rebalance burn upload + function rebalanceBurnUpload(RebalanceTypes.RebalanceBurnUploadData calldata data) public override onlyOperator { + _innerPing(); + _rebalanceBurnUpload(data); + // emit event + emit RebalanceBurnUpload(data.rebalanceId); } - // eventUploadDataAction - function eventUploadDataAction(PerpTypes.EventUpload calldata data) public override onlyOperator { + // @notice Function for rebalance mint upload + function rebalanceMintUpload(RebalanceTypes.RebalanceMintUploadData calldata data) public override onlyOperator { _innerPing(); - eventUploadData(data); + _rebalanceMintUpload(data); + // emit event + emit RebalanceMintUpload(data.rebalanceId); } - // futures trade upload data - function futuresTradeUploadData(PerpTypes.FuturesTradeUploadData memory data) internal { - require(data.batchId == futuresUploadBatchId, "batchId not match"); - PerpTypes.FuturesTradeUpload[] memory trades = data.trades; // gas saving - require(trades.length == data.count, "count not match"); - _validatePerp(trades); + /// @notice Function to verify Engine signature for futures trade upload data, if validated then Ledger contract will be called to execute the trade process + function _futuresTradeUploadData(PerpTypes.FuturesTradeUploadData calldata data) internal { + PerpTypes.FuturesTradeUpload[] calldata trades = data.trades; + if (trades.length != data.count) revert CountNotMatch(trades.length, data.count); + + // check engine signature + bool succ = Signature.perpUploadEncodeHashVerify(data, enginePerpTradeUploadAddress); + if (!succ) revert SignatureNotMatch(); + // process each validated perp trades for (uint256 i = 0; i < data.count; i++) { _processValidatedFutures(trades[i]); } - // update_futuresUploadBatchId - futuresUploadBatchId += 1; } - // validate futres trade upload data - function _validatePerp(PerpTypes.FuturesTradeUpload[] memory trades) internal pure { - for (uint256 i = 0; i < trades.length; i++) { - // check symbol (and maybe other value) is valid - // TODO - } + /// @notice Cross-Contract call to Ledger contract to process each validated perp future trades + function _processValidatedFutures(PerpTypes.FuturesTradeUpload calldata trade) internal { + ledger.executeProcessValidatedFutures(trade); } - // process each validated perp trades - function _processValidatedFutures(PerpTypes.FuturesTradeUpload memory trade) internal { - settlement.updateUserLedgerByTradeUpload(trade); - } + /// @notice Function to verify Engine signature for event upload data, if validated then Ledger contract will be called to execute the event process + function _eventUploadData(EventTypes.EventUpload calldata data) internal { + EventTypes.EventUploadData[] calldata events = data.events; // gas saving + if (events.length != data.count) revert CountNotMatch(events.length, data.count); + + // check engine signature + bool succ = Signature.eventsUploadEncodeHashVerify(data, engineEventUploadAddress); + if (!succ) revert SignatureNotMatch(); - // event upload data - function eventUploadData(PerpTypes.EventUpload memory data) internal { - require(data.batchId == eventUploadBatchId, "batchId not match"); - PerpTypes.EventUploadData[] memory events = data.events; // gas saving - require(events.length == data.count, "count not match"); // process each event upload for (uint256 i = 0; i < data.count; i++) { _processEventUpload(events[i]); } - // update_eventUploadBatchId - eventUploadBatchId += 1; } - // process each event upload - function _processEventUpload(PerpTypes.EventUploadData memory data) internal { - uint256 index_withdraw = 0; - uint256 index_settlement = 0; - uint256 index_liquidation = 0; - // iterate sequence to process each event. The sequence decides the event type. - for (uint256 i = 0; i < data.sequence.length; i++) { - if (data.sequence[i] == 0) { - // withdraw - settlement.executeWithdrawAction(data.withdraws[index_withdraw], data.eventId); - index_withdraw += 1; - } else if (data.sequence[i] == 1) { - // settlement - settlement.executeSettlement(data.settlements[index_settlement], data.eventId); - index_settlement += 1; - } else if (data.sequence[i] == 2) { - // liquidation - settlement.executeLiquidation(data.liquidations[index_liquidation], data.eventId); - index_liquidation += 1; - } else { - revert("invalid sequence"); - } + /// @notice Cross-Contract call to Ledger contract to process each event upload according to the event type + function _processEventUpload(EventTypes.EventUploadData calldata data) internal { + uint8 bizType = data.bizType; + if (bizType == 1) { + // withdraw + ledger.executeWithdrawAction(abi.decode(data.data, (EventTypes.WithdrawData)), data.eventId); + } else if (bizType == 2) { + // settlement + ledger.executeSettlement(abi.decode(data.data, (EventTypes.Settlement)), data.eventId); + } else if (bizType == 3) { + // adl + ledger.executeAdl(abi.decode(data.data, (EventTypes.Adl)), data.eventId); + } else if (bizType == 4) { + // liquidation + ledger.executeLiquidation(abi.decode(data.data, (EventTypes.Liquidation)), data.eventId); + } else if (bizType == 5) { + // fee disuribution + ledger.executeFeeDistribution(abi.decode(data.data, (EventTypes.FeeDistribution)), data.eventId); + } else if (bizType == 6) { + // delegate signer + ledger.executeDelegateSigner(abi.decode(data.data, (EventTypes.DelegateSigner)), data.eventId); + } else if (bizType == 7) { + // delegate withdraw + ledger.executeDelegateWithdrawAction(abi.decode(data.data, (EventTypes.WithdrawData)), data.eventId); + } else { + revert InvalidBizType(bizType); } } + /// @notice Function to verify Engine signature for perpetual future price data, if validated then MarketManager contract will be called to execute the market process + function _perpMarketInfo(MarketTypes.UploadPerpPrice calldata data) internal { + // check engine signature + bool succ = Signature.marketUploadEncodeHashVerify(data, engineMarketUploadAddress); + if (!succ) revert SignatureNotMatch(); + // process perp market info + marketManager.updateMarketUpload(data); + } + + /// @notice Function to verify Engine signature for sum unitary fundings data, if validated then MarketManager contract will be called to execute the market process + function _perpMarketInfo(MarketTypes.UploadSumUnitaryFundings calldata data) internal { + // check engine signature + bool succ = Signature.marketUploadEncodeHashVerify(data, engineMarketUploadAddress); + if (!succ) revert SignatureNotMatch(); + // process perp market info + marketManager.updateMarketUpload(data); + } + + /// @notice Cross-Contract call to Ledger contract to process each validated rebalance burn + function _rebalanceBurnUpload(RebalanceTypes.RebalanceBurnUploadData calldata data) internal { + // check engine signature + bool succ = Signature.rebalanceBurnUploadEncodeHashVerify(data, engineRebalanceUploadAddress); + if (!succ) revert SignatureNotMatch(); + // process rebalance burn + ledger.executeRebalanceBurn(data); + } + + /// @notice Cross-Contract call to Ledger contract to process each validated rebalance mint + function _rebalanceMintUpload(RebalanceTypes.RebalanceMintUploadData calldata data) internal { + // check engine signature + bool succ = Signature.rebalanceMintUploadEncodeHashVerify(data, engineRebalanceUploadAddress); + if (!succ) revert SignatureNotMatch(); + // process rebalance mint + ledger.executeRebalanceMint(data); + } + + /// @notice Function to update last operator interaction timestamp function _innerPing() internal { lastOperatorInteraction = block.timestamp; } - function checkCefiDown() public view override returns (bool) { - return (lastOperatorInteraction + 1 days > block.timestamp); + /// @notice Function to check if the last operator interaction timestamp is over 3 days + function checkEngineDown() public view override returns (bool) { + return (lastOperatorInteraction + 3 days < block.timestamp); } } diff --git a/src/OperatorManagerComponent.sol b/src/OperatorManagerComponent.sol new file mode 100644 index 0000000..bf81a48 --- /dev/null +++ b/src/OperatorManagerComponent.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "./interface/IOperatorManagerComponent.sol"; +import "openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol"; + +/// @title A component of OperatorManager +/// @author Orderly_Rubick +/// @notice OperatorManagerComponent is an component which can only be called by operatorManager (setter) +abstract contract OperatorManagerComponent is IOperatorManagerComponent, OwnableUpgradeable { + // OperatorManager address + address public operatorManagerAddress; + + function initialize() external virtual override initializer { + __Ownable_init(); + } + + /// @notice only operatorManager + modifier onlyOperatorManager() { + if (msg.sender != operatorManagerAddress) revert OnlyOperatorManagerCanCall(); + _; + } + + /// @notice set operatorManagerAddress + function setOperatorManagerAddress(address _operatorManagerAddress) public override onlyOwner { + if (_operatorManagerAddress == address(0)) revert OperatorManagerAddressZero(); + emit ChangeOperatorManager(operatorManagerAddress, _operatorManagerAddress); + operatorManagerAddress = _operatorManagerAddress; + } +} diff --git a/src/Settlement.sol b/src/Settlement.sol deleted file mode 100644 index cad6ae6..0000000 --- a/src/Settlement.sol +++ /dev/null @@ -1,212 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.18; - -import "./interface/ISettlement.sol"; -import "openzeppelin-contracts/contracts/access/Ownable.sol"; -import "./library/FeeCollector.sol"; -import "./library/Utils.sol"; - -/** - * Settlement is responsible for saving traders' Account (balance, perpPosition, and other meta) - * and global state (e.g. futuresUploadBatchId) - * This contract should only have one in main-chain (avalanche) - */ -contract Settlement is FeeCollector, ISettlement { - // OperatorManager contract address - address public operatorManagerAddress; - // crossChainManagerAddress contract address - address public crossChainManagerAddress; - // globalWithdrawId - uint256 public globalWithdrawId; - // operatorTradesBatchId - uint256 public operatorTradesBatchId; - // globalEventId - uint256 public globalEventId; - // userLedger accountId -> Account - mapping(bytes32 => AccountTypes.Account) private userLedger; - // insuranceFundAccountId - bytes32 private insuranceFundAccountId; - // valut balance, used for check if withdraw is valid - mapping(uint256 => mapping(bytes32 => uint256)) chain2symbol2balance; - - // require operator - modifier onlyOperatorManager() { - require(msg.sender == operatorManagerAddress, "only operator can call"); - _; - } - - // require crossChainManager - modifier onlyCrossChainManager() { - require(msg.sender == crossChainManagerAddress, "only crossChainManager can call"); - _; - } - - // set operatorManagerAddress - function setOperatorManagerAddress(address _operatorManagerAddress) public onlyOwner { - operatorManagerAddress = _operatorManagerAddress; - } - - // set crossChainManagerAddress - function setCrossChainManagerAddress(address _crossChainManagerAddress) public onlyOwner { - crossChainManagerAddress = _crossChainManagerAddress; - } - - // set insuranceFundAccountId - function setInsuranceFundAccountId(bytes32 _insuranceFundAccountId) public onlyOwner { - insuranceFundAccountId = _insuranceFundAccountId; - } - - // constructor - // call `setInsuranceFundAccountId` later - constructor(address _operatorManagerAddress, address _crossChainManagerAddress) { - operatorManagerAddress = _operatorManagerAddress; - crossChainManagerAddress = _crossChainManagerAddress; - } - - // get userLedger balance - function getUserLedgerBalance(bytes32 accountId, bytes32 symbol) public view returns (uint256) { - return userLedger[accountId].balances[symbol]; - } - - // get userLedger brokerId - function getUserLedgerBrokerId(bytes32 accountId) public view returns (bytes32) { - return userLedger[accountId].brokerId; - } - - // Interface implementation - - function accountRegister(AccountTypes.AccountRegister calldata data) public override onlyOperatorManager { - // check account not exist - require(userLedger[data.accountId].primaryAddress == address(0), "account already registered"); - // check accountId is correct by Utils.getAccountId - require(data.accountId == Utils.getAccountId(data.addr, data.brokerId), "accountId not match"); - AccountTypes.Account storage account = userLedger[data.accountId]; - account.primaryAddress = data.addr; - EnumerableSet.add(account.addresses, data.addr); - account.brokerId = data.brokerId; - // emit register event - emit AccountRegister(data.accountId, data.brokerId, data.addr); - } - - function accountDeposit(AccountTypes.AccountDeposit calldata data) public override onlyCrossChainManager { - // a not registerd account can still deposit, because of the consistency - AccountTypes.Account storage account = userLedger[data.accountId]; - account.balances[data.symbol] += data.amount; - chain2symbol2balance[data.chainId][data.symbol] += data.amount; - // emit deposit event - emit AccountDeposit(data.accountId, data.addr, data.symbol, data.chainId, data.amount); - } - - function updateUserLedgerByTradeUpload(PerpTypes.FuturesTradeUpload calldata trade) - public - override - onlyOperatorManager - { - AccountTypes.Account storage account = userLedger[trade.accountId]; - account.lastPerpTradeId = trade.tradeId; - // TODO update account.prep_position - } - - function executeWithdrawAction(PerpTypes.WithdrawData calldata withdraw, uint256 eventId) - public - override - onlyOperatorManager - { - AccountTypes.Account storage account = userLedger[withdraw.accountId]; - // require balance enough - require(account.balances[withdraw.symbol] >= withdraw.amount, "user balance not enough"); - // require addr is in account.addresses - require(EnumerableSet.contains(account.addresses, withdraw.addr), "addr not in account"); - // require chain has enough balance - require( - chain2symbol2balance[withdraw.chainId][withdraw.symbol] >= withdraw.amount, - "target chain balance not enough" - ); - // update balance - account.balances[withdraw.symbol] -= withdraw.amount; - chain2symbol2balance[withdraw.chainId][withdraw.symbol] -= withdraw.amount; - // TODO @Lewis send cross-chain tx - account.lastCefiEventId = eventId; - // emit withdraw event - emit AccountWithdraw(withdraw.accountId, withdraw.addr, withdraw.symbol, withdraw.chainId, withdraw.amount); - } - - function executeSettlement(PerpTypes.Settlement calldata settlement, uint256 eventId) - public - override - onlyOperatorManager - { - // check total settle amount zero - int256 totalSettleAmount = 0; - // gas saving - uint256 length = settlement.settlementExecutions.length; - PerpTypes.SettlementExecution[] calldata settlementExecutions = settlement.settlementExecutions; - for (uint256 i = 0; i < length; ++i) { - totalSettleAmount += settlementExecutions[i].settledAmount; - } - require(totalSettleAmount == 0, "total settle amount not zero"); - - AccountTypes.Account storage account = userLedger[settlement.accountId]; - uint256 balance = account.balances[settlement.settledAsset]; - account.hasPendingSettlementRequest = false; - if (settlement.insuranceTransferAmount != 0) { - // transfer insurance fund - if (int256(balance) + int256(settlement.insuranceTransferAmount) + settlement.settledAmount < 0) { - // overflow - revert("Insurance transfer amount invalid"); - } - AccountTypes.Account storage insuranceFund = userLedger[insuranceFundAccountId]; - insuranceFund.balances[settlement.settledAsset] += settlement.insuranceTransferAmount; - } - // for-loop settlement execution - for (uint256 i = 0; i < length; ++i) { - PerpTypes.SettlementExecution calldata settlementExecution = settlementExecutions[i]; - AccountTypes.PerpPosition storage position = account.perpPositions[settlementExecution.symbol]; - if (position.positionQty != 0) { - AccountTypes.chargeFundingFee(position, settlementExecution.sumUnitaryFundings); - position.cost_position += settlementExecution.settledAmount; - position.lastExecutedPrice = settlementExecution.markPrice; - } - // check balance + settledAmount >= 0, where balance should cast to int256 first - require(int256(balance) + settlementExecution.settledAmount >= 0, "balance not enough"); - balance = uint256(int256(balance) + settlementExecution.settledAmount); - } - account.lastCefiEventId = eventId; - } - - function executeLiquidation(PerpTypes.Liquidation calldata liquidation, uint256 eventId) - public - override - onlyOperatorManager - { - AccountTypes.Account storage liquidated_user = userLedger[liquidation.accountId]; - // for-loop liquidation execution - uint256 length = liquidation.liquidationTransfers.length; - PerpTypes.LiquidationTransfer[] calldata liquidationTransfers = liquidation.liquidationTransfers; - // chargeFundingFee for liquidated_user.perpPosition - for (uint256 i = 0; i < length; ++i) { - AccountTypes.chargeFundingFee( - liquidated_user.perpPositions[liquidation.liquidatedAsset], liquidationTransfers[i].sumUnitaryFundings - ); - } - // TODO get_liquidation_info - // TODO transfer_liquidatedAsset_to_insurance if insuranceTransferAmount != 0 - for (uint256 i = 0; i < length; ++i) { - // TODO liquidator_liquidate_and_update_eventId - // TODO liquidated_user_liquidate - // TODO insurance_liquidate - } - liquidated_user.lastCefiEventId = eventId; - // TODO emit event - } - - function _new_globalEventId() internal returns (uint256) { - globalEventId += 1; - return globalEventId; - } - - function _new_withdrawId() internal returns (uint256) { - globalWithdrawId += 1; - return globalWithdrawId; - } -} diff --git a/src/Vault.sol b/src/Vault.sol deleted file mode 100644 index 364358f..0000000 --- a/src/Vault.sol +++ /dev/null @@ -1,68 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.18; - -import "./interface/IVault.sol"; -import "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; -import "openzeppelin-contracts/contracts/security/ReentrancyGuard.sol"; -import "openzeppelin-contracts/contracts/access/Ownable.sol"; - -/** - * Vault is responsible for saving user's USDC (where USDC which is a IERC20 token). - * EACH CHAIN SHOULD HAVE ONE Vault CONTRACT. - * User can deposit USDC from Vault. - * Only crossChainManager can approve withdraw request. - */ -contract Vault is IVault, ReentrancyGuard, Ownable { - // equal to `Utils.string2HashedBytes32('USDC')` - bytes32 constant USDC = bytes32(uint256(0x61fc29e9a6b4b52b423e75ca44734454f94ea60ddff3dc47af01a2a646fe9572)); - // cross-chain operator address - address public crossChainManager; - // symbol to token address mapping - mapping(bytes32 => IERC20) public symbol2TokenAddress; - - // only cross-chain manager can call - modifier onlyCrossChainManager() { - require(msg.sender == crossChainManager, "only crossChainManager can call"); - _; - } - - // change crossChainManager - function setCrossChainManager(address _crossChainManager) public onlyOwner { - crossChainManager = _crossChainManager; - } - - // add token address - function addTokenAddress(bytes32 _symbol, address _tokenAddress) public onlyOwner { - symbol2TokenAddress[_symbol] = IERC20(_tokenAddress); - } - - // call `setCrossChainManager` later - constructor(address _usdcAddress) { - symbol2TokenAddress[USDC] = IERC20(_usdcAddress); - } - - // user deposit USDC - function deposit(bytes32 accountId, bytes32 tokenSymbol, uint256 tokenAmount) public { - IERC20 tokenAddress = symbol2TokenAddress[tokenSymbol]; - require(tokenAddress.transferFrom(msg.sender, address(this), tokenAmount), "transferFrom failed"); - // emit deposit event - emit DepositEvent(accountId, msg.sender, tokenSymbol, tokenAmount); - // TODO @Lewis send cross-chain tx to settlement - } - - // user withdraw USDC - function withdraw(bytes32 accountId, address addr, bytes32 tokenSymbol, uint256 tokenAmount) - public - override - onlyCrossChainManager - nonReentrant - { - IERC20 tokenAddress = symbol2TokenAddress[tokenSymbol]; - // check balane gt amount - require(tokenAddress.balanceOf(address(this)) >= tokenAmount, "balance not enough"); - // transfer to user - require(tokenAddress.transfer(addr, tokenAmount), "transfer failed"); - // emit withdraw event - emit WithdrawEvent(accountId, addr, tokenSymbol, tokenAmount); - } -} diff --git a/src/VaultCrossChainManager.sol b/src/VaultCrossChainManager.sol deleted file mode 100644 index 80781fa..0000000 --- a/src/VaultCrossChainManager.sol +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.18; - -import "./interface/IVault.sol"; -import "./interface/IVaultCrossChainManager.sol"; -import "openzeppelin-contracts/contracts/access/Ownable.sol"; - -contract VaultCrossChainManager is IVaultCrossChainManager, Ownable { - IVault public vault; - - // construct - constructor(address _vault) { - vault = IVault(_vault); - } - - // user withdraw USDC - function withdraw(CrossChainMessageTypes.MessageV1 calldata message) public override onlyOwner { - vault.withdraw(message.accountId, message.addr, message.tokenSymbol, message.tokenAmount); - } -} diff --git a/src/VaultManager.sol b/src/VaultManager.sol new file mode 100644 index 0000000..a62d835 --- /dev/null +++ b/src/VaultManager.sol @@ -0,0 +1,303 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "./interface/IVaultManager.sol"; +import "./LedgerComponent.sol"; +import "openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol"; + +/// @title Ledger call this manager for update vault data +/// @author Orderly_Rubick +/// @notice VaultManager is responsible for saving vaults' balance, to ensure the cross-chain tx should success +/// @notice VaultManager also saves the allowed brokerIds, tokenHash, symbolHash +contract VaultManager is IVaultManager, LedgerComponent { + using EnumerableSet for EnumerableSet.Bytes32Set; + + // A mapping to record how much balance each token has on each chain: tokenHash => chainId => balance + mapping(bytes32 => mapping(uint256 => uint128)) public tokenBalanceOnchain; + // A mapping to record how much balance each token has been frozen on each chain: tokenHash => chainId => frozenBalance + mapping(bytes32 => mapping(uint256 => uint128)) public tokenFrozenBalanceOnchain; + // A mapping to record which token has been allowed on each chain: tokenHash => chainId => allowed + mapping(bytes32 => mapping(uint256 => bool)) public allowedChainToken; // supported token on each chain + + // A set to record supported tokenHash + EnumerableSet.Bytes32Set private allowedTokenSet; // supported token + // A set to record supported brokerHash + EnumerableSet.Bytes32Set private allowedBrokerSet; // supported broker + // A set to record supported symbolHash, this symbol means the trading pair, such BTC_USDC_PERP + EnumerableSet.Bytes32Set private allowedSymbolSet; // supported symbol + + mapping(bytes32 => uint128) public maxWithdrawFee; // default = unlimited + + // A mapping to record how much balance each token has been burn-frozen on each chain: tokenHash => chainId => frozenBalance + mapping(bytes32 => mapping(uint256 => uint128)) public tokenBurnFrozenBalanceOnchain; + // A mapping to record CCTP domain + mapping(uint256 => uint32) public chain2cctpDomain; + // A mapping to record vault address + mapping(uint256 => address) public chain2VaultAddress; + + // rebalanceStatus key is mod by this constant + uint64 constant MAX_REBALACE_SLOT = 100; + // for record latest rebalance status + mapping(uint64 => RebalanceTypes.RebalanceStatus) private rebalanceStatus; + + constructor() { + _disableInitializers(); + } + + function initialize() external override(IVaultManager, LedgerComponent) initializer { + __Ownable_init(); + setAllowedBroker(0x6ca2f644ef7bd6d75953318c7f2580014941e753b3c6d54da56b3bf75dd14dfc, true); // woofi_pro + setAllowedToken(0xd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa, true); // USDC + setAllowedChainToken(0xd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa, 42161, true); // Arbitrum + setAllowedSymbol(0xa2adc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d46610b, true); // PERP_NEAR_USDC + setAllowedSymbol(0x7e83089239db756ee233fa8972addfea16ae653db0f692e4851aed546b21caeb, true); // PERP_ETH_USDC + setAllowedSymbol(0x5a8133e52befca724670dbf2cade550c522c2410dd5b1189df675e99388f509d, true); // PERP_BTC_USDC + setAllowedSymbol(0x5d0471b083610a6f3b572fc8b0f759c5628e74159816681fb7d927b9263de60b, true); // PERP_WOO_USDC + } + + /// @notice frozen & finish frozen + function frozenBalance(bytes32 _tokenHash, uint256 _chainId, uint128 _deltaBalance) external override onlyLedger { + tokenBalanceOnchain[_tokenHash][_chainId] -= _deltaBalance; + tokenFrozenBalanceOnchain[_tokenHash][_chainId] += _deltaBalance; + } + + function finishFrozenBalance(bytes32 _tokenHash, uint256 _chainId, uint128 _deltaBalance) + external + override + onlyLedger + { + tokenFrozenBalanceOnchain[_tokenHash][_chainId] -= _deltaBalance; + } + + /// @notice Get the token balance on the Vault contract given the tokenHash and chainId + function getBalance(bytes32 _tokenHash, uint256 _chainId) public view override returns (uint128) { + return tokenBalanceOnchain[_tokenHash][_chainId]; + } + + /// @notice Increase the token balance on the Vault contract given the tokenHash and chainId + function addBalance(bytes32 _tokenHash, uint256 _chainId, uint128 _deltaBalance) external override onlyLedger { + tokenBalanceOnchain[_tokenHash][_chainId] += _deltaBalance; + } + + /// @notice Decrease the token balance on the Vault contract given the tokenHash and chainId + function subBalance(bytes32 _tokenHash, uint256 _chainId, uint128 _deltaBalance) external override onlyLedger { + tokenBalanceOnchain[_tokenHash][_chainId] -= _deltaBalance; + } + + /// @notice Get the frozen token balance on the Vault contract given the tokenHash and chainId + function getFrozenBalance(bytes32 _tokenHash, uint256 _chainId) public view override returns (uint128) { + return tokenFrozenBalanceOnchain[_tokenHash][_chainId]; + } + + /// @notice Set the status for a broker given the brokerHash + function setAllowedBroker(bytes32 _brokerHash, bool _allowed) public override onlyOwner { + bool succ = false; + if (_allowed) { + succ = allowedBrokerSet.add(_brokerHash); + } else { + succ = allowedBrokerSet.remove(_brokerHash); + } + if (!succ) revert EnumerableSetError(); + emit SetAllowedBroker(_brokerHash, _allowed); + } + + /// @notice Get the status for a broker given the brokerHash + function getAllowedBroker(bytes32 _brokerHash) public view override returns (bool) { + return allowedBrokerSet.contains(_brokerHash); + } + + /// @notice Set the status for a token given the tokenHash and chainId + function setAllowedChainToken(bytes32 _tokenHash, uint256 _chainId, bool _allowed) public override onlyOwner { + allowedChainToken[_tokenHash][_chainId] = _allowed; + emit SetAllowedChainToken(_tokenHash, _chainId, _allowed); + } + + /// @notice Get the status for a token given the tokenHash and chainId + function getAllowedChainToken(bytes32 _tokenHash, uint256 _chainId) public view override returns (bool) { + return allowedTokenSet.contains(_tokenHash) && allowedChainToken[_tokenHash][_chainId]; + } + + /// @notice Set the status for a symbol given the symbolHash + function setAllowedSymbol(bytes32 _symbolHash, bool _allowed) public override onlyOwner { + bool succ = false; + if (_allowed) { + succ = allowedSymbolSet.add(_symbolHash); + } else { + succ = allowedSymbolSet.remove(_symbolHash); + } + if (!succ) revert EnumerableSetError(); + emit SetAllowedSymbol(_symbolHash, _allowed); + } + + /// @notice Get the status for a symbol given the symbolHash + function getAllowedSymbol(bytes32 _symbolHash) public view override returns (bool) { + return allowedSymbolSet.contains(_symbolHash); + } + + /// @notice Get all allowed tokenHash + function getAllAllowedToken() public view override returns (bytes32[] memory) { + return allowedTokenSet.values(); + } + + /// @notice Get all allowed brokerHash + function getAllAllowedBroker() public view override returns (bytes32[] memory) { + return allowedBrokerSet.values(); + } + + /// @notice Get all allowed symbolHash + function getAllAllowedSymbol() public view override returns (bytes32[] memory) { + return allowedSymbolSet.values(); + } + + /// @notice Set the status for a token given the tokenHash + function setAllowedToken(bytes32 _tokenHash, bool _allowed) public override onlyOwner { + bool succ = false; + if (_allowed) { + succ = allowedTokenSet.add(_tokenHash); + } else { + succ = allowedTokenSet.remove(_tokenHash); + } + if (!succ) revert EnumerableSetError(); + emit SetAllowedToken(_tokenHash, _allowed); + } + + /// @notice Get the status for a token given the tokenHash + function getAllowedToken(bytes32 _tokenHash) public view override returns (bool) { + return allowedTokenSet.contains(_tokenHash); + } + + /// @notice Set maxWithdrawFee + function setMaxWithdrawFee(bytes32 _tokenHash, uint128 _maxWithdrawFee) public override onlyOwner { + maxWithdrawFee[_tokenHash] = _maxWithdrawFee; + emit SetMaxWithdrawFee(_tokenHash, _maxWithdrawFee); + } + + /// @notice Get maxWithdrawFee + function getMaxWithdrawFee(bytes32 _tokenHash) public view override returns (uint128) { + return maxWithdrawFee[_tokenHash]; + } + + // chain2cctpDomain & chain2VaultAddress + + function setChain2cctpMeta(uint256 chainId, uint32 cctpDomain, address vaultAddress) external override onlyOwner { + if (vaultAddress == address(0)) revert AddressZero(); + chain2cctpDomain[chainId] = cctpDomain; + chain2VaultAddress[chainId] = vaultAddress; + } + + // burn & mint with CCTP + function executeRebalanceBurn(RebalanceTypes.RebalanceBurnUploadData calldata data) + external + override + onlyLedger + returns (uint32, address) + { + // check if the burn request is valid + RebalanceTypes.RebalanceStatus storage status = rebalanceStatus[data.rebalanceId % MAX_REBALACE_SLOT]; + if (status.rebalanceId == data.rebalanceId) { + if (status.burnStatus == RebalanceTypes.RebalanceStatusEnum.Pending) { + revert RebalanceStillPending(); + } else if (status.burnStatus == RebalanceTypes.RebalanceStatusEnum.Succ) { + revert RebalanceAlreadySucc(); + } + } + // record rebalance status + rebalanceStatus[data.rebalanceId % MAX_REBALACE_SLOT] = RebalanceTypes.RebalanceStatus({ + rebalanceId: data.rebalanceId, + burnStatus: RebalanceTypes.RebalanceStatusEnum.Pending, + mintStatus: RebalanceTypes.RebalanceStatusEnum.None + }); + burnToken(data.tokenHash, data.burnChainId, data.amount); + uint32 dstDomain = chain2cctpDomain[data.mintChainId]; + address dstVaultAddress = chain2VaultAddress[data.mintChainId]; + // domain can be 0, so do not check domain. But vaultAddress should not be 0, check that. + if (dstVaultAddress == address(0)) revert RebalanceChainIdInvalid(data.mintChainId); + // check token is supported on both chain + if (!getAllowedChainToken(data.tokenHash, data.burnChainId)) { + revert RebalanceTokenNotSupported(data.tokenHash, data.burnChainId); + } + if (!getAllowedChainToken(data.tokenHash, data.mintChainId)) { + revert RebalanceTokenNotSupported(data.tokenHash, data.mintChainId); + } + emit RebalanceBurn(data.rebalanceId, data.amount, data.tokenHash, data.burnChainId, data.mintChainId); + return (dstDomain, dstVaultAddress); + } + + function rebalanceBurnFinish(RebalanceTypes.RebalanceBurnCCFinishData calldata data) external override onlyLedger { + RebalanceTypes.RebalanceStatus storage status = rebalanceStatus[data.rebalanceId % MAX_REBALACE_SLOT]; + // check if the rebalanceId is valid + if (status.rebalanceId != data.rebalanceId) { + revert RebalanceIdNotMatch(data.rebalanceId, status.rebalanceId); + } + if (data.success) { + finishBurnToken(data.tokenHash, data.burnChainId, data.amount); + status.burnStatus = RebalanceTypes.RebalanceStatusEnum.Succ; + } else { + cancelBurnToken(data.tokenHash, data.burnChainId, data.amount); + status.burnStatus = RebalanceTypes.RebalanceStatusEnum.Fail; + } + emit RebalanceBurnResult(data.rebalanceId, data.success); + } + + function executeRebalanceMint(RebalanceTypes.RebalanceMintUploadData calldata data) external override onlyLedger { + // check if the mint request is valid + RebalanceTypes.RebalanceStatus storage status = rebalanceStatus[data.rebalanceId % MAX_REBALACE_SLOT]; + if (status.rebalanceId == data.rebalanceId) { + if (status.burnStatus != RebalanceTypes.RebalanceStatusEnum.Succ) { + revert RebalanceMintUnexpected(); + } + if (status.mintStatus == RebalanceTypes.RebalanceStatusEnum.Pending) { + revert RebalanceStillPending(); + } else if (status.mintStatus == RebalanceTypes.RebalanceStatusEnum.Succ) { + revert RebalanceAlreadySucc(); + } + } else { + revert RebalanceMintUnexpected(); + } + // record rebalance status + status.mintStatus = RebalanceTypes.RebalanceStatusEnum.Pending; + emit RebalanceMint(data.rebalanceId); + } + + function rebalanceMintFinish(RebalanceTypes.RebalanceMintCCFinishData calldata data) external override onlyLedger { + RebalanceTypes.RebalanceStatus storage status = rebalanceStatus[data.rebalanceId % MAX_REBALACE_SLOT]; + // check if the rebalanceId is valid + if (status.rebalanceId != data.rebalanceId) { + revert RebalanceIdNotMatch(data.rebalanceId, status.rebalanceId); + } + if (data.success) { + finishMintToken(data.tokenHash, data.mintChainId, data.amount); + status.mintStatus = RebalanceTypes.RebalanceStatusEnum.Succ; + } else { + status.mintStatus = RebalanceTypes.RebalanceStatusEnum.Fail; + } + emit RebalanceMintResult(data.rebalanceId, data.success); + } + + function getRebalanceStatus(uint64 rebalanceId) + public + view + override + returns (RebalanceTypes.RebalanceStatus memory) + { + return rebalanceStatus[rebalanceId % MAX_REBALACE_SLOT]; + } + + function burnToken(bytes32 _tokenHash, uint256 _chainId, uint128 _amount) internal { + tokenBalanceOnchain[_tokenHash][_chainId] -= _amount; + tokenBurnFrozenBalanceOnchain[_tokenHash][_chainId] += _amount; + } + + function finishBurnToken(bytes32 _tokenHash, uint256 _chainId, uint128 _amount) internal { + tokenBurnFrozenBalanceOnchain[_tokenHash][_chainId] -= _amount; + } + + function cancelBurnToken(bytes32 _tokenHash, uint256 _chainId, uint128 _amount) internal { + tokenBalanceOnchain[_tokenHash][_chainId] += _amount; + tokenBurnFrozenBalanceOnchain[_tokenHash][_chainId] -= _amount; + } + + function finishMintToken(bytes32 _tokenHash, uint256 _chainId, uint128 _amount) internal { + tokenBalanceOnchain[_tokenHash][_chainId] += _amount; + } +} diff --git a/src/dataLayout/LedgerDataLayout.sol b/src/dataLayout/LedgerDataLayout.sol new file mode 100644 index 0000000..7836386 --- /dev/null +++ b/src/dataLayout/LedgerDataLayout.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "../library/typesHelper/AccountTypeHelper.sol"; +import "../library/types/RebalanceTypes.sol"; +import "../interface/IVaultManager.sol"; +import "../interface/IMarketManager.sol"; +import "../interface/IFeeManager.sol"; + +/// @title Ledger contract data layout +/// @author Orderly_Rubick +/// @notice DataLayout for Ledger contract, align with 50 slots +contract LedgerDataLayout { + // A mapping from accountId to Orderly Account + mapping(bytes32 => AccountTypes.Account) internal userLedger; + // The OperatorManager contract address + address public operatorManagerAddress; + // The crossChainManagerAddress contract address + address public crossChainManagerAddress; + // The interface for VaultManager contract + IVaultManager public vaultManager; + // The interface for MarketManager contract + IMarketManager public marketManager; + // An increasing global event Id, for event trade upload + uint64 public globalEventId; + // The interface for FeeManager contract + IFeeManager public feeManager; + // An incresing global deposit Id for cross chain deposit + uint64 public globalDepositId; + // A mapping from contract accountId to its delegate signer + mapping(bytes32 => AccountTypes.AccountDelegateSigner) public contractSigner; + + // The storage gap to prevent overwriting by proxy + uint256[43] private __gap; +} diff --git a/src/dataLayout/OperatorManagerDataLayout.sol b/src/dataLayout/OperatorManagerDataLayout.sol new file mode 100644 index 0000000..70ea4b6 --- /dev/null +++ b/src/dataLayout/OperatorManagerDataLayout.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol"; +import "../interface/ILedger.sol"; +import "../interface/IMarketManager.sol"; + +/// @title OperatorManager contract data layout +/// @author Orderly_Rubick +/// @notice DataLayout for OperatorManager contract, align with 50 slots +contract OperatorManagerDataLayout { + // An EOA operator address + address public operatorAddress; + // The ledger Interface + ILedger public ledger; + // An increasing Id for the batch of perpetual futures trading upload from Operator + uint64 public futuresUploadBatchId; + // The market manager Interface + IMarketManager public marketManager; + // An increasing Id for the event upload from Operator + uint64 public eventUploadBatchId; + + // The last operator interaction timestamp + uint256 public lastOperatorInteraction; + + // The signature addresses of Engine + address public engineSpotTradeUploadAddress; + address public enginePerpTradeUploadAddress; + address public engineEventUploadAddress; + address public engineMarketUploadAddress; + address public engineRebalanceUploadAddress; + + // For OperatorManagerZip contract, which calldata is zipped to reduce L1 gas cost + address public operatorManagerZipAddress; + + // The storage gap to prevent overwriting by proxy + uint256[40] private __gap; +} diff --git a/src/dataLayout/OperatorManagerZipDataLayout.sol b/src/dataLayout/OperatorManagerZipDataLayout.sol new file mode 100644 index 0000000..2cdabde --- /dev/null +++ b/src/dataLayout/OperatorManagerZipDataLayout.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "../interface/IOperatorManager.sol"; +import "../library/types/PerpTypes.sol"; + +/// @title OperatorManagerZip contract data layout +/// @author Orderly_Zion +/// @notice DataLayout for OperatorManagerZip contract, align with 50 slots +contract OperatorManagerZipDataLayout { + // An EOA operator address, for zip contract to call + address public zipOperatorAddress; + // The opeartorManager Interface + IOperatorManager public operatorManager; + // mapping symbolHash from uint8 to bytes32 + mapping(uint8 => bytes32) public symbolId2Hash; + + // The storage gap to prevent overwriting by proxy + uint256[47] private __gap; +} diff --git a/src/interface/ICrossChainManager.sol b/src/interface/ICrossChainManager.sol deleted file mode 100644 index 88b649b..0000000 --- a/src/interface/ICrossChainManager.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.18; - -import "../library/types/OperatorTypes.sol"; -import "../library/types/CrossChainMessageTypes.sol"; - -interface ICrossChainManager { - // @deprecated TODO should be removed - function crossChainOperatorExecuteAction( - OperatorTypes.CrossChainOperatorActionData actionData, - bytes calldata action - ) external; - - // cross chain call deposit - function deposit(CrossChainMessageTypes.MessageV1 calldata message) external; -} diff --git a/src/interface/IFeeManager.sol b/src/interface/IFeeManager.sol new file mode 100644 index 0000000..70d2649 --- /dev/null +++ b/src/interface/IFeeManager.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "./ILedgerComponent.sol"; + +interface IFeeManager is ILedgerComponent { + // Fee collector type definition + enum FeeCollectorType { + None, + WithdrawFeeCollector, + FuturesFeeCollector + } + + // Event for fee collector change + event ChangeFeeCollector( + FeeCollectorType indexed feeCollectorType, bytes32 oldFeeCollector, bytes32 newFeeCollector + ); + + event ChangeBrokerAccountId(bytes32 oldBrokerAccountId, bytes32 newBrokerAccountId); + + function initialize() external; + + function getFeeCollector(FeeCollectorType feeCollectorType) external returns (bytes32); + function changeFeeCollector(FeeCollectorType feeCollectorType, bytes32 _newCollector) external; + + function setBrokerAccountId(bytes32 brokerId, bytes32 brokerAccountId) external; +} diff --git a/src/interface/ILedger.sol b/src/interface/ILedger.sol new file mode 100644 index 0000000..ad3843d --- /dev/null +++ b/src/interface/ILedger.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "../library/types/AccountTypes.sol"; +import "../library/types/PerpTypes.sol"; +import "../library/types/EventTypes.sol"; +import "../library/types/MarketTypes.sol"; +import "../library/types/RebalanceTypes.sol"; +import "./error/IError.sol"; +import "./ILedgerEvent.sol"; + +// Defines the error, event and ABI. +// Data should be stored in LedgerDataLayout, NOT in this contract. +interface ILedger is IError, ILedgerEvent { + function initialize() external; + + // Functions called by cross chain manager on Ledger side + function accountDeposit(AccountTypes.AccountDeposit calldata data) external; + function accountWithDrawFinish(AccountTypes.AccountWithdraw calldata withdraw) external; + + // Functions called by operator manager to executre actions + function executeProcessValidatedFutures(PerpTypes.FuturesTradeUpload calldata trade) external; + function executeWithdrawAction(EventTypes.WithdrawData calldata withdraw, uint64 eventId) external; + function executeSettlement(EventTypes.Settlement calldata ledger, uint64 eventId) external; + function executeLiquidation(EventTypes.Liquidation calldata liquidation, uint64 eventId) external; + function executeAdl(EventTypes.Adl calldata adl, uint64 eventId) external; + function executeFeeDistribution(EventTypes.FeeDistribution calldata feeDistribution, uint64 eventId) external; + function executeDelegateSigner(EventTypes.DelegateSigner calldata delegateSigner, uint64 eventId) external; + function executeDelegateWithdrawAction(EventTypes.WithdrawData calldata withdraw, uint64 eventId) external; + function executeRebalanceBurn(RebalanceTypes.RebalanceBurnUploadData calldata data) external; + function rebalanceBurnFinish(RebalanceTypes.RebalanceBurnCCFinishData calldata data) external; + function executeRebalanceMint(RebalanceTypes.RebalanceMintUploadData calldata data) external; + function rebalanceMintFinish(RebalanceTypes.RebalanceMintCCFinishData calldata data) external; + + // view call + function getFrozenWithdrawNonce(bytes32 accountId, uint64 withdrawNonce, bytes32 tokenHash) + external + view + returns (uint128); + // omni view call + function batchGetUserLedger(bytes32[] calldata accountIds, bytes32[] memory tokens, bytes32[] memory symbols) + external + view + returns (AccountTypes.AccountSnapshot[] memory); + function batchGetUserLedger(bytes32[] calldata accountIds) + external + view + returns (AccountTypes.AccountSnapshot[] memory); + + // admin call + function setOperatorManagerAddress(address _operatorManagerAddress) external; + function setCrossChainManager(address _crossChainManagerAddress) external; + function setVaultManager(address _vaultManagerAddress) external; + function setMarketManager(address _marketManagerAddress) external; + function setFeeManager(address _feeManagerAddress) external; + function setLedgerImplA(address _ledgerImplA) external; +} diff --git a/src/interface/ILedgerComponent.sol b/src/interface/ILedgerComponent.sol new file mode 100644 index 0000000..430e340 --- /dev/null +++ b/src/interface/ILedgerComponent.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "./error/IError.sol"; + +interface ILedgerComponent is IError { + event ChangeLedger(address oldAddress, address newAddress); + + function initialize() external; + + // set ledgerAddress + function setLedgerAddress(address _ledgerAddress) external; +} diff --git a/src/interface/ILedgerCrossChainManager.sol b/src/interface/ILedgerCrossChainManager.sol new file mode 100644 index 0000000..2e5609a --- /dev/null +++ b/src/interface/ILedgerCrossChainManager.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +// Importing types +import "../library/types/EventTypes.sol"; +import "../library/types/AccountTypes.sol"; +import "../library/types/RebalanceTypes.sol"; + +/// @title ILedgerCrossChainManager Interface +/// @notice Interface for managing cross-chain activities related to the ledger. +interface ILedgerCrossChainManager { + /// @notice Approves a cross-chain withdrawal from the ledger to the vault. + /// @param data Struct containing withdrawal data. + function withdraw(EventTypes.WithdrawData memory data) external; + + /// @notice Approves a cross-chain burn from the ledger to the vault. + /// @param data Struct containing burn data. + function burn(RebalanceTypes.RebalanceBurnCCData memory data) external; + + /// @notice Approves a cross-chain mint from the vault to the ledger. + /// @param data Struct containing mint data. + function mint(RebalanceTypes.RebalanceMintCCData memory data) external; + + /// @notice Sets the ledger address. + /// @param ledger Address of the new ledger. + function setLedger(address ledger) external; + + /// @notice Sets the operator manager address. + /// @param operatorManager Address of the new operator manager. + function setOperatorManager(address operatorManager) external; + + /// @notice Sets the cross-chain relay address. + /// @param crossChainRelay Address of the new cross-chain relay. + function setCrossChainRelay(address crossChainRelay) external; +} diff --git a/src/interface/ILedgerEvent.sol b/src/interface/ILedgerEvent.sol new file mode 100644 index 0000000..e356eac --- /dev/null +++ b/src/interface/ILedgerEvent.sol @@ -0,0 +1,220 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +interface ILedgerEvent { + event AccountRegister(bytes32 indexed accountId, bytes32 indexed brokerId, address indexed userAddress); + event AccountDeposit( + bytes32 indexed accountId, + uint64 indexed depositNonce, + uint64 indexed eventId, + address userAddress, + bytes32 tokenHash, + uint128 tokenAmount, + uint256 srcChainId, + uint64 srcChainDepositNonce, + bytes32 brokerHash + ); + event AccountWithdrawApprove( + bytes32 indexed accountId, + uint64 indexed withdrawNonce, + uint64 indexed eventId, + bytes32 brokerHash, + address sender, + address receiver, + uint256 chainId, + bytes32 tokenHash, + uint128 tokenAmount, + uint128 fee + ); + event AccountWithdrawFinish( + bytes32 indexed accountId, + uint64 indexed withdrawNonce, + uint64 indexed eventId, + bytes32 brokerHash, + address sender, + address receiver, + uint256 chainId, + bytes32 tokenHash, + uint128 tokenAmount, + uint128 fee + ); + event AccountWithdrawFail( + bytes32 indexed accountId, + uint64 indexed withdrawNonce, + uint64 indexed eventId, + bytes32 brokerHash, + address sender, + address receiver, + uint256 chainId, + bytes32 tokenHash, + uint128 tokenAmount, + uint128 fee, + uint8 failReson + ); + + event SettlementResult( + uint64 indexed eventId, + bytes32 indexed accountId, + int128 settledAmount, + bytes32 settledAssetHash, + bytes32 insuranceAccountId, + uint128 insuranceTransferAmount, + uint64 settlementExecutionsCount, + uint64 lastEngineEventId + ); + + event AdlResult( + uint64 indexed eventId, + bytes32 indexed accountId, + bytes32 insuranceAccountId, + bytes32 symbolHash, + int128 positionQtyTransfer, + int128 costPositionTransfer, + uint128 adlPrice, + int128 sumUnitaryFundings, + uint64 lastEngineEventId + ); + + event LiquidationResult( + uint64 indexed eventId, + bytes32 indexed liquidatedAccountId, + bytes32 indexed insuranceAccountId, + bytes32 liquidatedAssetHash, + uint128 insuranceTransferAmount, + uint64 lastEngineEventId + ); + + event ProcessValidatedFutures( + bytes32 indexed accountId, + bytes32 indexed symbolHash, + bytes32 feeAssetHash, + int128 tradeQty, + int128 notional, + uint128 executedPrice, + int128 fee, + int128 sumUnitaryFundings, + uint64 tradeId, + uint64 matchId, + uint64 timestamp, + bool side + ); + + event SettlementExecution( + bytes32 indexed symbolHash, uint128 markPrice, int128 sumUnitaryFundings, int128 settledAmount + ); + event LiquidationTransfer( + uint64 indexed liquidationTransferId, + bytes32 indexed liquidatorAccountId, + bytes32 indexed symbolHash, + int128 positionQtyTransfer, + int128 costPositionTransfer, + int128 liquidatorFee, + int128 insuranceFee, + int128 liquidationFee, + uint128 markPrice, + int128 sumUnitaryFundings + ); + + event FeeDistribution( + uint64 indexed eventId, + bytes32 indexed fromAccountId, + bytes32 indexed toAccountId, + uint128 amount, + bytes32 tokenHash + ); + + event DelegateSigner( + uint64 indexed eventId, + uint256 indexed chainId, + bytes32 indexed accountId, + address delegateContract, + bytes32 brokerHash, + address delegateSigner + ); + + event ChangeOperatorManager(address oldAddress, address newAddress); + event ChangeCrossChainManager(address oldAddress, address newAddress); + event ChangeVaultManager(address oldAddress, address newAddress); + event ChangeMarketManager(address oldAddress, address newAddress); + event ChangeFeeManager(address oldAddress, address newAddress); + event ChangeLedgerImplA(address oldAddress, address newAddress); + + // All events below are deprecated + // Keep them for indexer backward compatibility + + // @deprecated + event AccountRegister( + bytes32 indexed accountId, bytes32 indexed brokerId, address indexed userAddress, uint256 blocktime + ); + // @deprecated + event AccountDeposit( + bytes32 indexed accountId, + uint64 indexed depositNonce, + uint64 indexed eventId, + address userAddress, + bytes32 tokenHash, + uint128 tokenAmount, + uint256 srcChainId, + uint64 srcChainDepositNonce, + bytes32 brokerHash, + uint256 blocktime + ); + // @deprecated + event AccountWithdrawApprove( + bytes32 indexed accountId, + uint64 indexed withdrawNonce, + uint64 indexed eventId, + bytes32 brokerHash, + address sender, + address receiver, + uint256 chainId, + bytes32 tokenHash, + uint128 tokenAmount, + uint128 fee, + uint256 blocktime + ); + // @deprecated + event AccountWithdrawFinish( + bytes32 indexed accountId, + uint64 indexed withdrawNonce, + uint64 indexed eventId, + bytes32 brokerHash, + address sender, + address receiver, + uint256 chainId, + bytes32 tokenHash, + uint128 tokenAmount, + uint128 fee, + uint256 blocktime + ); + // @deprecated + event AccountWithdrawFail( + bytes32 indexed accountId, + uint64 indexed withdrawNonce, + uint64 indexed eventId, + bytes32 brokerHash, + address sender, + address receiver, + uint256 chainId, + bytes32 tokenHash, + uint128 tokenAmount, + uint128 fee, + uint256 blocktime, + uint8 failReson + ); + // @deprecated + event ProcessValidatedFutures( + bytes32 indexed accountId, + bytes32 indexed symbolHash, + bytes32 feeAssetHash, + int128 tradeQty, + int128 notional, + uint128 executedPrice, + uint128 fee, + int128 sumUnitaryFundings, + uint64 tradeId, + uint64 matchId, + uint64 timestamp, + bool side + ); +} diff --git a/src/interface/ILedgerImplA.sol b/src/interface/ILedgerImplA.sol new file mode 100644 index 0000000..97325db --- /dev/null +++ b/src/interface/ILedgerImplA.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "../library/types/AccountTypes.sol"; +import "../library/types/PerpTypes.sol"; +import "../library/types/EventTypes.sol"; +import "../library/types/MarketTypes.sol"; +import "../library/types/RebalanceTypes.sol"; +import "./error/IError.sol"; +import "./ILedgerEvent.sol"; + +interface ILedgerImplA is IError, ILedgerEvent { + function initialize() external; + + // Functions called by cross chain manager on Ledger side + function accountDeposit(AccountTypes.AccountDeposit calldata data) external; + function accountWithDrawFinish(AccountTypes.AccountWithdraw calldata withdraw) external; + + // Functions called by operator manager to executre actions + function executeProcessValidatedFutures(PerpTypes.FuturesTradeUpload calldata trade) external; + function executeWithdrawAction(EventTypes.WithdrawData calldata withdraw, uint64 eventId) external; + function executeSettlement(EventTypes.Settlement calldata ledger, uint64 eventId) external; + function executeLiquidation(EventTypes.Liquidation calldata liquidation, uint64 eventId) external; + function executeAdl(EventTypes.Adl calldata adl, uint64 eventId) external; + function executeFeeDistribution(EventTypes.FeeDistribution calldata feeDistribution, uint64 eventId) external; + function executeDelegateSigner(EventTypes.DelegateSigner calldata delegateSigner, uint64 eventId) external; + function executeDelegateWithdrawAction(EventTypes.WithdrawData calldata withdraw, uint64 eventId) external; +} diff --git a/src/interface/IMarketManager.sol b/src/interface/IMarketManager.sol new file mode 100644 index 0000000..7cc7bbd --- /dev/null +++ b/src/interface/IMarketManager.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "../library/types/MarketTypes.sol"; +import "./ILedgerComponent.sol"; +import "./IOperatorManagerComponent.sol"; + +// MarketManager can be accessed by both Ledger and OperatorManager +interface IMarketManager is ILedgerComponent, IOperatorManagerComponent { + event MarketData(uint64 maxTimestamp); + event FundingData(uint64 maxTimestamp); + + function initialize() external override(ILedgerComponent, IOperatorManagerComponent); + + // update functions + function updateMarketUpload(MarketTypes.UploadPerpPrice calldata data) external; + function updateMarketUpload(MarketTypes.UploadSumUnitaryFundings calldata data) external; + + function setPerpMarketCfg(bytes32 _pairSymbol, MarketTypes.PerpMarketCfg memory _perpMarketCfg) external; + function getPerpMarketCfg(bytes32 _pairSymbol) external view returns (MarketTypes.PerpMarketCfg memory); + + // set functions + function setLastMarkPriceUpdated(bytes32 _pairSymbol, uint64 _lastMarkPriceUpdated) external; + function setLastFundingUpdated(bytes32 _pairSymbol, uint64 _lastFundingUpdated) external; +} diff --git a/src/interface/IOperatorManager.sol b/src/interface/IOperatorManager.sol index 5c6b9c4..faac0f3 100644 --- a/src/interface/IOperatorManager.sol +++ b/src/interface/IOperatorManager.sol @@ -1,27 +1,55 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.18; -import "../library/types/OperatorTypes.sol"; import "../library/types/AccountTypes.sol"; import "../library/types/PerpTypes.sol"; +import "../library/types/EventTypes.sol"; +import "../library/types/MarketTypes.sol"; +import "../library/types/RebalanceTypes.sol"; +import "./error/IError.sol"; -interface IOperatorManager { - // @deprecated TODO @Rubick should be removed - function operatorExecuteAction(OperatorTypes.OperatorActionData actionData, bytes calldata action) external; +interface IOperatorManager is IError { + event FuturesTradeUpload(uint64 indexed batchId); + event EventUpload(uint64 indexed batchId); + event ChangeEngineUpload(uint8 indexed types, address oldAddress, address newAddress); + event ChangeOperator(uint8 indexed types, address oldAddress, address newAddress); + event ChangeMarketManager(address oldAddress, address newAddress); + event ChangeLedger(address oldAddress, address newAddress); + event RebalanceBurnUpload(uint64 indexed rebalanceId); + event RebalanceMintUpload(uint64 indexed rebalanceId); + + // @depreacted + // All events below are deprecated + // Keep them for indexer backward compatibility + event FuturesTradeUpload(uint64 indexed batchId, uint256 blocktime); + event EventUpload(uint64 indexed batchId, uint256 blocktime); + + function initialize() external; - // operator call account register - function accountRegisterAction(AccountTypes.AccountRegister calldata data) external; // operator call futures trade upload - function futuresTradeUploadDataAction(PerpTypes.FuturesTradeUploadData calldata data) external; + function futuresTradeUpload(PerpTypes.FuturesTradeUploadData calldata data) external; // operator call event upload - function eventUploadDataAction(PerpTypes.EventUpload calldata data) external; + function eventUpload(EventTypes.EventUpload calldata data) external; + // operator call perp market info + function perpPriceUpload(MarketTypes.UploadPerpPrice calldata data) external; + function sumUnitaryFundingsUpload(MarketTypes.UploadSumUnitaryFundings calldata data) external; + // operator call rebalance mint + function rebalanceBurnUpload(RebalanceTypes.RebalanceBurnUploadData calldata) external; + function rebalanceMintUpload(RebalanceTypes.RebalanceMintUploadData calldata) external; // operator call ping function operatorPing() external; - // check if cefi down - function checkCefiDown() external returns (bool); + // check if engine down + function checkEngineDown() external returns (bool); // admin call - function setOperator(address _operator) external; - function setSettlement(address _settlement) external; + function setOperator(address _operatorAddress) external; + function setLedger(address _ledger) external; + function setMarketManager(address _marketManagerAddress) external; + function setEngineSpotTradeUploadAddress(address _engineSpotTradeUploadAddress) external; + function setEnginePerpTradeUploadAddress(address _enginePerpTradeUploadAddress) external; + function setEngineEventUploadAddress(address _engineEventUploadAddress) external; + function setEngineMarketUploadAddress(address _engineMarketUploadAddress) external; + function setEngineRebalanceUploadAddress(address _engineRebalanceUploadAddress) external; + function setOperatorManagerZipAddress(address _operatorManagerZipAddress) external; } diff --git a/src/interface/IOperatorManagerComponent.sol b/src/interface/IOperatorManagerComponent.sol new file mode 100644 index 0000000..b5be53e --- /dev/null +++ b/src/interface/IOperatorManagerComponent.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "./error/IError.sol"; + +interface IOperatorManagerComponent is IError { + event ChangeOperatorManager(address oldAddress, address newAddress); + + function initialize() external; + + // set operatorManagerAddress + function setOperatorManagerAddress(address _operatorManagerAddress) external; +} diff --git a/src/interface/IOperatorManagerZip.sol b/src/interface/IOperatorManagerZip.sol new file mode 100644 index 0000000..2059df3 --- /dev/null +++ b/src/interface/IOperatorManagerZip.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "../library/types/PerpTypes.sol"; +import "../library/types/PerpTypesZip.sol"; +import "./error/IError.sol"; + +interface IOperatorManagerZip is IError { + event ChangeOperator(address oldAddress, address newAddress); + event ChangeOperatorManager(address oldAddress, address newAddress); + + function initialize() external; + // admin call + function setOperator(address _operatorAddress) external; + function setOpeartorManager(address _operatorManager) external; + function setSymbol(bytes32 symbolHash, uint8 symbolId) external; + // opeartor call + function decodeFuturesTradeUploadData(bytes calldata data) external; + // misc + function initSymbolId2Hash() external; + function placeholder(PerpTypesZip.FuturesTradeUploadDataZip calldata zip) external; +} diff --git a/src/interface/ISettlement.sol b/src/interface/ISettlement.sol deleted file mode 100644 index 02cbff9..0000000 --- a/src/interface/ISettlement.sol +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.18; - -import "../library/types/AccountTypes.sol"; -import "../library/types/PerpTypes.sol"; - -interface ISettlement { - event AccountRegister(bytes32 indexed accountId, bytes32 indexed brokerId, address indexed addr); - event AccountDeposit( - bytes32 indexed accountId, - address indexed addr, - bytes32 indexed tokenSymbol, - uint256 srcChainId, - uint256 tokenAmount - ); - event AccountWithdraw( - bytes32 indexed accountId, - address indexed addr, - bytes32 indexed tokenSymbol, - uint256 dstChainId, - uint256 tokenAmount - ); - - function accountRegister(AccountTypes.AccountRegister calldata accountRegister) external; - function accountDeposit(AccountTypes.AccountDeposit calldata accountDeposit) external; - function updateUserLedgerByTradeUpload(PerpTypes.FuturesTradeUpload calldata trade) external; - function executeWithdrawAction(PerpTypes.WithdrawData calldata withdraw, uint256 eventId) external; - function executeSettlement(PerpTypes.Settlement calldata settlement, uint256 eventId) external; - function executeLiquidation(PerpTypes.Liquidation calldata liquidation, uint256 eventId) external; - - // view call - function getUserLedgerBalance(bytes32 accountId, bytes32 symbol) external view returns (uint256); - function getUserLedgerBrokerId(bytes32 accountId) external view returns (bytes32); -} diff --git a/src/interface/IVault.sol b/src/interface/IVault.sol index 0ddf75f..4374530 100644 --- a/src/interface/IVault.sol +++ b/src/interface/IVault.sol @@ -1,10 +1,89 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.18; +import "./../library/types/VaultTypes.sol"; +import "./../library/types/RebalanceTypes.sol"; + interface IVault { - event DepositEvent(bytes32 indexed accountId, address indexed addr, bytes32 indexed symbol, uint256 amount); - event WithdrawEvent(bytes32 indexed accountId, address indexed addr, bytes32 indexed symbol, uint256 amount); + error OnlyCrossChainManagerCanCall(); + error AccountIdInvalid(); + error TokenNotAllowed(); + error BrokerNotAllowed(); + error BalanceNotEnough(uint256 balance, uint128 amount); + error AddressZero(); + error EnumerableSetError(); + error ZeroDepositFee(); + error ZeroDeposit(); + error ZeroCodeLength(); + error NotZeroCodeLength(); + + // @deprecated + event AccountDeposit( + bytes32 indexed accountId, + address indexed userAddress, + uint64 indexed depositNonce, + bytes32 tokenHash, + uint128 tokenAmount + ); + + event AccountDepositTo( + bytes32 indexed accountId, + address indexed userAddress, + uint64 indexed depositNonce, + bytes32 tokenHash, + uint128 tokenAmount + ); + + event AccountWithdraw( + bytes32 indexed accountId, + uint64 indexed withdrawNonce, + bytes32 brokerHash, + address sender, + address receiver, + bytes32 tokenHash, + uint128 tokenAmount, + uint128 fee + ); + + event AccountDelegate( + address indexed delegateContract, + bytes32 indexed brokerHash, + address indexed delegateSigner, + uint256 chainId, + uint256 blockNumber + ); + + event SetAllowedToken(bytes32 indexed _tokenHash, bool _allowed); + event SetAllowedBroker(bytes32 indexed _brokerHash, bool _allowed); + event ChangeTokenAddressAndAllow(bytes32 indexed _tokenHash, address _tokenAddress); + event ChangeCrossChainManager(address oldAddress, address newAddress); + + function initialize() external; + + function deposit(VaultTypes.VaultDepositFE calldata data) external payable; + function depositTo(address receiver, VaultTypes.VaultDepositFE calldata data) external payable; + function getDepositFee(address recevier, VaultTypes.VaultDepositFE calldata data) external view returns (uint256); + function enableDepositFee(bool _enabled) external; + function withdraw(VaultTypes.VaultWithdraw calldata data) external; + function delegateSigner(VaultTypes.VaultDelegate calldata data) external; + + // CCTP: functions for receive rebalance msg + function rebalanceMint(RebalanceTypes.RebalanceMintCCData calldata data) external; + function rebalanceBurn(RebalanceTypes.RebalanceBurnCCData calldata data) external; + function setTokenMessengerContract(address _tokenMessengerContract) external; + function setRebalanceMessengerContract(address _rebalanceMessengerContract) external; + + // admin call + function setCrossChainManager(address _crossChainManagerAddress) external; + function emergencyPause() external; + function emergencyUnpause() external; - function deposit(bytes32 accountId, bytes32 tokenSymbol, uint256 tokenAmount) external; - function withdraw(bytes32 accountId, address addr, bytes32 tokenSymbol, uint256 tokenAmount) external; + // whitelist + function setAllowedToken(bytes32 _tokenHash, bool _allowed) external; + function setAllowedBroker(bytes32 _brokerHash, bool _allowed) external; + function changeTokenAddressAndAllow(bytes32 _tokenHash, address _tokenAddress) external; + function getAllowedToken(bytes32 _tokenHash) external view returns (address); + function getAllowedBroker(bytes32 _brokerHash) external view returns (bool); + function getAllAllowedToken() external view returns (bytes32[] memory); + function getAllAllowedBroker() external view returns (bytes32[] memory); } diff --git a/src/interface/IVaultCrossChainManager.sol b/src/interface/IVaultCrossChainManager.sol index 8c19a0c..7ad6c0b 100644 --- a/src/interface/IVaultCrossChainManager.sol +++ b/src/interface/IVaultCrossChainManager.sol @@ -1,8 +1,44 @@ -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT pragma solidity ^0.8.18; -import "../library/types/CrossChainMessageTypes.sol"; +// Importing necessary utility libraries and types +import "../library/types/AccountTypes.sol"; +import "../library/types/VaultTypes.sol"; +import "../library/types/RebalanceTypes.sol"; +/// @title IVaultCrossChainManager Interface +/// @notice Interface for managing cross-chain activities related to the vault. interface IVaultCrossChainManager { - function withdraw(CrossChainMessageTypes.MessageV1 calldata message) external; + /// @notice Triggers a withdrawal from the ledger. + /// @param withdraw Struct containing withdrawal data. + function withdraw(VaultTypes.VaultWithdraw memory withdraw) external; + + /// @notice Triggers a finish msg from vault to ledger to inform the status of burn + /// @param data Struct containing burn data. + function burnFinish(RebalanceTypes.RebalanceBurnCCFinishData memory data) external; + + /// @notice Triggers a finish msg from vault to ledger to inform the status of mint + /// @param data Struct containing mint data. + function mintFinish(RebalanceTypes.RebalanceMintCCFinishData memory data) external; + + /// @notice Initiates a deposit to the vault. + /// @param data Struct containing deposit data. + function deposit(VaultTypes.VaultDeposit memory data) external; + + /// @notice Initiates a deposit to the vault along with native fees. + /// @param data Struct containing deposit data. + function depositWithFee(VaultTypes.VaultDeposit memory data) external payable; + + /// @notice Fetches the deposit fee based on deposit data. + /// @param data Struct containing deposit data. + /// @return fee The calculated deposit fee. + function getDepositFee(VaultTypes.VaultDeposit memory data) external view returns (uint256); + + /// @notice Sets the vault address. + /// @param vault Address of the new vault. + function setVault(address vault) external; + + /// @notice Sets the cross-chain relay address. + /// @param crossChainRelay Address of the new cross-chain relay. + function setCrossChainRelay(address crossChainRelay) external; } diff --git a/src/interface/IVaultManager.sol b/src/interface/IVaultManager.sol new file mode 100644 index 0000000..e77dba6 --- /dev/null +++ b/src/interface/IVaultManager.sol @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "./ILedgerComponent.sol"; +import "../library/types/RebalanceTypes.sol"; +import "./error/IError.sol"; + +interface IVaultManager is IError, ILedgerComponent { + function initialize() external; + + // event + event SetAllowedBroker(bytes32 indexed _brokerHash, bool _allowed); + event SetAllowedSymbol(bytes32 indexed _symbolHash, bool _allowed); + event SetAllowedToken(bytes32 indexed _tokenHash, bool _allowed); + event SetAllowedChainToken(bytes32 indexed _tokenHash, uint256 indexed _chainId, bool _allowed); + event SetMaxWithdrawFee(bytes32 indexed _tokenHash, uint128 _maxWithdrawFee); + + // rebalance burn token + event RebalanceBurn( + uint64 indexed rebalanceId, uint128 amount, bytes32 tokenHash, uint256 srcChainId, uint256 dstChainId + ); + // rebalance mint token + event RebalanceMint(uint64 indexed rebalanceId); + // rebalance burn result + event RebalanceBurnResult(uint64 indexed rebalanceId, bool success); + // rebalance mint result + event RebalanceMintResult(uint64 indexed rebalanceId, bool success); + + // get balance + function getBalance(bytes32 _tokenHash, uint256 _chainId) external view returns (uint128); + // add balance + function addBalance(bytes32 _tokenHash, uint256 _chainId, uint128 _deltaBalance) external; + // sub balance + function subBalance(bytes32 _tokenHash, uint256 _chainId, uint128 _deltaBalance) external; + + // get frozen balance + function getFrozenBalance(bytes32 _tokenHash, uint256 _chainId) external view returns (uint128); + + // frozen & finish frozen + function frozenBalance(bytes32 _tokenHash, uint256 _chainId, uint128 _deltaBalance) external; + function finishFrozenBalance(bytes32 _tokenHash, uint256 _chainId, uint128 _deltaBalance) external; + + // allow broker + function setAllowedBroker(bytes32 _brokerHash, bool _allowed) external; + function getAllowedBroker(bytes32 _brokerHash) external view returns (bool); + + // allow chain+token. in some chain, some token is not allowed for safety + function setAllowedChainToken(bytes32 _tokenHash, uint256 _chainId, bool _allowed) external; + function getAllowedChainToken(bytes32 _tokenHash, uint256 _chainId) external view returns (bool); + + // allow token + function setAllowedToken(bytes32 _tokenHash, bool _allowed) external; + function getAllowedToken(bytes32 _tokenHash) external view returns (bool); + + // allow symbol + function setAllowedSymbol(bytes32 _symbolHash, bool _allowed) external; + function getAllowedSymbol(bytes32 _symbolHash) external view returns (bool); + + // get allowed set + function getAllAllowedToken() external view returns (bytes32[] memory); + function getAllAllowedBroker() external view returns (bytes32[] memory); + function getAllAllowedSymbol() external view returns (bytes32[] memory); + + // maxWithdrawFee + function setMaxWithdrawFee(bytes32 _tokenHash, uint128 _maxWithdrawFee) external; + function getMaxWithdrawFee(bytes32 _tokenHash) external view returns (uint128); + + // chain2cctpDomain & chain2VaultAddress + function setChain2cctpMeta(uint256 chainId, uint32 cctpDomain, address vaultAddress) external; + + // burn & mint with CCTP + function executeRebalanceBurn(RebalanceTypes.RebalanceBurnUploadData calldata data) + external + returns (uint32, address); + function rebalanceBurnFinish(RebalanceTypes.RebalanceBurnCCFinishData calldata data) external; + function executeRebalanceMint(RebalanceTypes.RebalanceMintUploadData calldata data) external; + function rebalanceMintFinish(RebalanceTypes.RebalanceMintCCFinishData calldata data) external; + function getRebalanceStatus(uint64 rebalanceId) external view returns (RebalanceTypes.RebalanceStatus memory); +} diff --git a/src/interface/cctp/IMessageTransmitter.sol b/src/interface/cctp/IMessageTransmitter.sol new file mode 100644 index 0000000..903a44d --- /dev/null +++ b/src/interface/cctp/IMessageTransmitter.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +interface IMessageTransmitter { + function receiveMessage(bytes calldata message, bytes calldata attestation) external returns (bool success); +} diff --git a/src/interface/cctp/ITokenMessenger.sol b/src/interface/cctp/ITokenMessenger.sol new file mode 100644 index 0000000..415361b --- /dev/null +++ b/src/interface/cctp/ITokenMessenger.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +interface ITokenMessenger { + function depositForBurn(uint256 amount, uint32 destinationDomain, bytes32 mintRecipient, address burnToken) + external + returns (uint64 _nonce); +} diff --git a/src/interface/error/IError.sol b/src/interface/error/IError.sol new file mode 100644 index 0000000..65fae29 --- /dev/null +++ b/src/interface/error/IError.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +// All errors should be defined here +interface IError { + // Common Error + error AddressZero(); + error Bytes32Zero(); + error DelegatecallFail(); + + // LedgerComponent Error + error OnlyLedgerCanCall(); + error LedgerAddressZero(); + + // OperatorManager Error + error OnlyOperatorManagerCanCall(); + error OperatorManagerAddressZero(); + + // Ledger Error + error OnlyOperatorCanCall(); + error OnlyCrossChainManagerCanCall(); + error TotalSettleAmountNotMatch(int128 amount); + error BalanceNotEnough(uint128 balance, int128 amount); + error InsuranceTransferToSelf(); + error InsuranceTransferAmountInvalid(uint128 balance, uint128 insuranceTransferAmount, int128 settledAmount); + error UserPerpPositionQtyZero(bytes32 accountId, bytes32 symbolHash); + error InsurancePositionQtyInvalid(int128 adlPositionQtyTransfer, int128 userPositionQty); + error AccountIdInvalid(); + error TokenNotAllowed(bytes32 tokenHash, uint256 chainId); + error BrokerNotAllowed(); + error SymbolNotAllowed(); + error DelegateSignerNotMatch(bytes32 accountId, address savedSginer, address givenSigner); + error DelegateChainIdNotMatch(bytes32 accountId, uint256 savedChainId, uint256 givenChainId); + error DelegateReceiverNotMatch(address receiver, address delegateContract); + error ZeroChainId(); + error ZeroDelegateSigner(); + error ZeroDelegateContract(); + + // OperatorManager Error + error InvalidBizType(uint8 bizType); + error BatchIdNotMatch(uint64 batchId, uint64 futuresUploadBatchId); + error CountNotMatch(uint256 length, uint256 count); + error SignatureNotMatch(); + + // VaultManager Error + error EnumerableSetError(); + error RebalanceIdNotMatch(uint64 givenId, uint64 wantId); // the given rebalanceId not match the latest rebalanceId + error RebalanceStillPending(); // the rebalance is still pending, so no need to upload again + error RebalanceAlreadySucc(); // the rebalance is already succ, so no need to upload again + error RebalanceMintUnexpected(); // the rebalance burn state or something is wrong, so the rebalance mint is unexpected. Should never happen. + error RebalanceChainIdInvalid(uint256 chainId); + error RebalanceTokenNotSupported(bytes32 tokenHash, uint256 chainId); + + // FeeManager Error + error InvalidFeeCollectorType(); + + // OperatorManagerZip Error + error SymbolNotRegister(); + + // Libraray Error + // AccountTypeHelper + error FrozenBalanceInconsistent(); // should never happen + // SafeCastHelper + error SafeCastOverflow(); + error SafeCastUnderflow(); +} diff --git a/src/library/FeeCollector.sol b/src/library/FeeCollector.sol deleted file mode 100644 index 43ce886..0000000 --- a/src/library/FeeCollector.sol +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.18; - -import "openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol"; -import "openzeppelin-contracts/contracts/access/Ownable.sol"; - -// WIP @Rubick -abstract contract FeeCollector is Ownable { - using EnumerableSet for EnumerableSet.Bytes32Set; - - struct Fee { - // balance - mapping(bytes32 => uint256) balances; - // other meta - uint256 feeRate; - } - - bytes32 public feeCollectorAddress; - EnumerableSet.Bytes32Set private brokerIdSet; - // Fee is a struct contains balance, feeRate, etc. - mapping(bytes32 => Fee) id2FeeValue; - - // set feeCollectorAddress - function setFeeCollectorAddress(bytes32 _feeCollectorAddress) public onlyOwner { - feeCollectorAddress = _feeCollectorAddress; - } - - // add balance to feeCollector - function addFeeCollectorBalance(bytes32 symbol, uint256 _balance) public onlyOwner { - id2FeeValue[feeCollectorAddress].balances[symbol] += _balance; - } - - // add new brokerId - function addBrokerId(bytes32 _brokerId) public onlyOwner { - require(!EnumerableSet.contains(brokerIdSet, _brokerId), "brokerId already exist"); - EnumerableSet.add(brokerIdSet, _brokerId); - } - - // add balance to brokerId - function addBalance(bytes32 _brokerId, uint256 _balance) public onlyOwner { - require(EnumerableSet.contains(brokerIdSet, _brokerId), "brokerId not exist"); - Fee storage fee = id2FeeValue[_brokerId]; - fee.balances[_brokerId] += _balance; - } - - // get brokerId List. O(n) - function getBrokerIdList() public view returns (bytes32[] memory) { - return EnumerableSet.values(brokerIdSet); - } -} diff --git a/src/library/Signature.sol b/src/library/Signature.sol index e0e38e8..9f67bdc 100644 --- a/src/library/Signature.sol +++ b/src/library/Signature.sol @@ -1,35 +1,360 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.18; +import "openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol"; +import "./types/PerpTypes.sol"; +import "./types/EventTypes.sol"; +import "./types/MarketTypes.sol"; +import "./types/RebalanceTypes.sol"; + +/// @title Signature library +/// @author Orderly_Rubick, Orderly_Zion library Signature { - function getEthSignedMessageHash(bytes32 _messageHash) public pure returns (bytes32) { - /* - Signature is produced by signing a keccak256 hash with the following format: - "\x19Ethereum Signed Message\n" + len(msg) + msg - */ - return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", _messageHash)); - } - - // verify - function verify(bytes32 hash, bytes memory signature, address signer) internal pure returns (bool) { - require(signature.length == 65, "invalid signature length"); - - bytes32 r; - bytes32 s; - uint8 v; - - assembly { - r := mload(add(signature, 0x20)) - s := mload(add(signature, 0x40)) - v := byte(0, mload(add(signature, 0x60))) + function verifyWithdraw(address sender, EventTypes.WithdrawData memory data) internal view returns (bool) { + bytes32 typeHash = + keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); + + bytes32 eip712DomainHash = keccak256( + abi.encode(typeHash, keccak256(bytes("Orderly")), keccak256(bytes("1")), data.chainId, address(this)) + ); + + bytes32 hashStruct = keccak256( + abi.encode( + keccak256( + "Withdraw(string brokerId,uint256 chainId,address receiver,string token,uint256 amount,uint64 withdrawNonce,uint64 timestamp)" + ), + keccak256(abi.encodePacked(data.brokerId)), + data.chainId, + data.receiver, + keccak256(abi.encodePacked(data.tokenSymbol)), + data.tokenAmount, + data.withdrawNonce, + data.timestamp + ) + ); + + bytes32 hash = keccak256(abi.encodePacked("\x19\x01", eip712DomainHash, hashStruct)); + return ECDSA.recover(hash, data.v, data.r, data.s) == sender; + } + + function verifyDelegateWithdraw(address delegateSigner, EventTypes.WithdrawData memory data) + internal + view + returns (bool) + { + bytes32 typeHash = + keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); + + bytes32 eip712DomainHash = keccak256( + abi.encode(typeHash, keccak256(bytes("Orderly")), keccak256(bytes("1")), data.chainId, address(this)) + ); + + bytes32 hashStruct = keccak256( + abi.encode( + keccak256( + "DelegateWithdraw(address delegateContract,string brokerId,uint256 chainId,address receiver,string token,uint256 amount,uint64 withdrawNonce,uint64 timestamp)" + ), + data.sender, + keccak256(abi.encodePacked(data.brokerId)), + data.chainId, + data.receiver, + keccak256(abi.encodePacked(data.tokenSymbol)), + data.tokenAmount, + data.withdrawNonce, + data.timestamp + ) + ); + + bytes32 hash = keccak256(abi.encodePacked("\x19\x01", eip712DomainHash, hashStruct)); + return ECDSA.recover(hash, data.v, data.r, data.s) == delegateSigner; + } + + function verify(bytes32 hash, bytes32 r, bytes32 s, uint8 v, address signer) internal pure returns (bool) { + return ECDSA.recover(hash, v, r, s) == signer; + } + + function perpUploadEncodeHashVerify(PerpTypes.FuturesTradeUploadData memory data, address signer) + internal + pure + returns (bool) + { + bytes memory encoded = abi.encode(data.batchId, data.count, data.trades); + bytes32 h = ECDSA.toEthSignedMessageHash(keccak256(encoded)); + return verify(h, data.r, data.s, data.v, signer); + } + + struct WithdrawDataSignature { + uint64 eventId; // flat map to this + string brokerId; + bytes32 accountId; + uint256 chainId; + address sender; + address receiver; + string token; + uint128 tokenAmount; + uint128 fee; + uint64 withdrawNonce; + uint64 timestamp; + } + + struct SettlementSignature { + uint64 eventId; // flat map to this + bytes32 accountId; + int128 settledAmount; + bytes32 settledAssetHash; + bytes32 insuranceAccountId; + uint128 insuranceTransferAmount; + EventTypes.SettlementExecution[] settlementExecutions; + uint64 timestamp; + } + + struct AdlSignature { + uint64 eventId; // flat map to this + bytes32 accountId; + bytes32 insuranceAccountId; + bytes32 symbolHash; + int128 positionQtyTransfer; + int128 costPositionTransfer; + uint128 adlPrice; + int128 sumUnitaryFundings; + uint64 timestamp; + } + + struct LiquidationSignature { + uint64 eventId; // flat map to this + bytes32 liquidatedAccountId; + bytes32 insuranceAccountId; + uint128 insuranceTransferAmount; + bytes32 liquidatedAssetHash; + EventTypes.LiquidationTransfer[] liquidationTransfers; + uint64 timestamp; + } + + struct FeeDistributionSignature { + uint64 eventId; // flat map to this + bytes32 fromAccountId; + bytes32 toAccountId; + uint128 amount; + bytes32 tokenHash; + } + + struct DelegeteSignerSignature { + uint64 eventId; // flat map to this + address delegateSigner; + address delegateContract; + bytes32 brokerHash; + uint256 chainId; + } + + struct EventUploadSignature { + uint64 batchId; + WithdrawDataSignature[] withdraws; + SettlementSignature[] settlements; + AdlSignature[] adls; + LiquidationSignature[] liquidations; + FeeDistributionSignature[] feeDistributions; + DelegeteSignerSignature[] delegateSigners; + } + + function eventsUploadEncodeHash(EventTypes.EventUpload memory data) internal pure returns (bytes memory) { + // counArray is used to count the number of each event type + // countArray[0]: withdraws, countArray[1]: settlements, countArray[2]: adls, countArray[3]: liquidations, countArray[4]: feeDistributions, countArray[5]: delegateSigners, countArray[6]: delegateWithdraws + // countArray2 is used to count the number of each filed signature structure inside EventUploadSignature, because the event withdraw and event delegateWith share the common WithdrawDataSignature[], + // so we have `withdraws: new WithdrawDataSignature[](countArray[0]+countArray[6])` when initializing eventUploadSignature + uint8[] memory countArray = new uint8[](7); + uint8[] memory countArray2 = new uint8[](7); // 0: withdraws + delegate, 1: settlements, 2: adls, 3: liquidations, 4: feeDistributions, 5: delegateSigners 6: null + uint256 len = data.events.length; + for (uint256 i = 0; i < len; i++) { + countArray[data.events[i].bizType - 1]++; } + EventUploadSignature memory eventUploadSignature = EventUploadSignature({ + batchId: data.batchId, + withdraws: new WithdrawDataSignature[](countArray[0]+countArray[6]), + settlements: new SettlementSignature[](countArray[1]), + adls: new AdlSignature[](countArray[2]), + liquidations: new LiquidationSignature[](countArray[3]), + feeDistributions: new FeeDistributionSignature[](countArray[4]), + delegateSigners: new DelegeteSignerSignature[](countArray[5]) + }); - if (v < 27) { - v += 27; + for (uint256 i = 0; i < len; i++) { + EventTypes.EventUploadData memory eventUploadData = data.events[i]; + if (eventUploadData.bizType == 1 || eventUploadData.bizType == 7) { + EventTypes.WithdrawData memory withdrawData = + abi.decode(eventUploadData.data, (EventTypes.WithdrawData)); + WithdrawDataSignature memory withdrawDataSignature = WithdrawDataSignature({ + eventId: eventUploadData.eventId, + brokerId: withdrawData.brokerId, + accountId: withdrawData.accountId, + chainId: withdrawData.chainId, + sender: withdrawData.sender, + receiver: withdrawData.receiver, + token: withdrawData.tokenSymbol, + tokenAmount: withdrawData.tokenAmount, + fee: withdrawData.fee, + withdrawNonce: withdrawData.withdrawNonce, + timestamp: withdrawData.timestamp + }); + eventUploadSignature.withdraws[countArray2[0]] = withdrawDataSignature; + countArray2[0]++; + } else if (eventUploadData.bizType == 2) { + EventTypes.Settlement memory settlement = abi.decode(eventUploadData.data, (EventTypes.Settlement)); + SettlementSignature memory settlementSignature = SettlementSignature({ + eventId: eventUploadData.eventId, + accountId: settlement.accountId, + settledAmount: settlement.settledAmount, + settledAssetHash: settlement.settledAssetHash, + insuranceAccountId: settlement.insuranceAccountId, + insuranceTransferAmount: settlement.insuranceTransferAmount, + settlementExecutions: settlement.settlementExecutions, + timestamp: settlement.timestamp + }); + eventUploadSignature.settlements[countArray2[1]] = settlementSignature; + countArray2[1]++; + } else if (eventUploadData.bizType == 3) { + EventTypes.Adl memory adl = abi.decode(eventUploadData.data, (EventTypes.Adl)); + AdlSignature memory adlSignature = AdlSignature({ + eventId: eventUploadData.eventId, + accountId: adl.accountId, + insuranceAccountId: adl.insuranceAccountId, + symbolHash: adl.symbolHash, + positionQtyTransfer: adl.positionQtyTransfer, + costPositionTransfer: adl.costPositionTransfer, + adlPrice: adl.adlPrice, + sumUnitaryFundings: adl.sumUnitaryFundings, + timestamp: adl.timestamp + }); + eventUploadSignature.adls[countArray2[2]] = adlSignature; + countArray2[2]++; + } else if (eventUploadData.bizType == 4) { + EventTypes.Liquidation memory liquidation = abi.decode(eventUploadData.data, (EventTypes.Liquidation)); + LiquidationSignature memory liquidationSignature = LiquidationSignature({ + eventId: eventUploadData.eventId, + liquidatedAccountId: liquidation.liquidatedAccountId, + insuranceAccountId: liquidation.insuranceAccountId, + insuranceTransferAmount: liquidation.insuranceTransferAmount, + liquidatedAssetHash: liquidation.liquidatedAssetHash, + liquidationTransfers: liquidation.liquidationTransfers, + timestamp: liquidation.timestamp + }); + eventUploadSignature.liquidations[countArray2[3]] = liquidationSignature; + countArray2[3]++; + } else if (eventUploadData.bizType == 5) { + EventTypes.FeeDistribution memory feeDistribution = + abi.decode(eventUploadData.data, (EventTypes.FeeDistribution)); + FeeDistributionSignature memory feeDistributionSignature = FeeDistributionSignature({ + eventId: eventUploadData.eventId, + fromAccountId: feeDistribution.fromAccountId, + toAccountId: feeDistribution.toAccountId, + amount: feeDistribution.amount, + tokenHash: feeDistribution.tokenHash + }); + eventUploadSignature.feeDistributions[countArray2[4]] = feeDistributionSignature; + countArray2[4]++; + } else if (eventUploadData.bizType == 6) { + EventTypes.DelegateSigner memory delegateSigner = + abi.decode(eventUploadData.data, (EventTypes.DelegateSigner)); + DelegeteSignerSignature memory delegeteSignerSignature = DelegeteSignerSignature({ + eventId: eventUploadData.eventId, + delegateSigner: delegateSigner.delegateSigner, + delegateContract: delegateSigner.delegateContract, + brokerHash: delegateSigner.brokerHash, + chainId: delegateSigner.chainId + }); + eventUploadSignature.delegateSigners[countArray2[5]] = delegeteSignerSignature; + countArray2[5]++; + } } + bytes memory encoded; + if (eventUploadSignature.delegateSigners.length > 0) { + // v3 signature, only support [v2 delegateSigners] + encoded = abi.encode( + eventUploadSignature.batchId, + eventUploadSignature.withdraws, + eventUploadSignature.settlements, + eventUploadSignature.adls, + eventUploadSignature.liquidations, + eventUploadSignature.feeDistributions, + eventUploadSignature.delegateSigners + ); + } else if (eventUploadSignature.feeDistributions.length > 0) { + // v2 signature, only support [v1, feeDistributions] + encoded = abi.encode( + eventUploadSignature.batchId, + eventUploadSignature.withdraws, + eventUploadSignature.settlements, + eventUploadSignature.adls, + eventUploadSignature.liquidations, + eventUploadSignature.feeDistributions + ); + } else { + // v1 signature, only support [withdraws(+delegateWithdraw), settlements, adls, liquidations] + encoded = abi.encode( + eventUploadSignature.batchId, + eventUploadSignature.withdraws, + eventUploadSignature.settlements, + eventUploadSignature.adls, + eventUploadSignature.liquidations + ); + } + return encoded; + } - require(v == 27 || v == 28, "invalid signature 'v' value"); + function eventsUploadEncodeHashVerify(EventTypes.EventUpload memory data, address signer) + internal + pure + returns (bool) + { + bytes32 h = ECDSA.toEthSignedMessageHash(keccak256(eventsUploadEncodeHash(data))); + return verify(h, data.r, data.s, data.v, signer); + } + + function marketUploadEncodeHashVerify(MarketTypes.UploadPerpPrice memory data, address signer) + internal + pure + returns (bool) + { + bytes memory encoded = abi.encode(data.maxTimestamp, data.perpPrices); + bytes32 h = ECDSA.toEthSignedMessageHash(keccak256(encoded)); + return verify(h, data.r, data.s, data.v, signer); + } + + function marketUploadEncodeHashVerify(MarketTypes.UploadSumUnitaryFundings memory data, address signer) + internal + pure + returns (bool) + { + bytes memory encoded = abi.encode(data.maxTimestamp, data.sumUnitaryFundings); + bytes32 h = ECDSA.toEthSignedMessageHash(keccak256(encoded)); + return verify(h, data.r, data.s, data.v, signer); + } + + function rebalanceBurnUploadEncodeHashVerify(RebalanceTypes.RebalanceBurnUploadData memory data, address signer) + internal + pure + returns (bool) + { + bytes memory encoded = + abi.encode(data.rebalanceId, data.amount, data.tokenHash, data.burnChainId, data.mintChainId); + bytes32 h = ECDSA.toEthSignedMessageHash(keccak256(encoded)); + return verify(h, data.r, data.s, data.v, signer); + } - return ecrecover(hash, v, r, s) == signer; + function rebalanceMintUploadEncodeHashVerify(RebalanceTypes.RebalanceMintUploadData memory data, address signer) + internal + pure + returns (bool) + { + bytes memory encoded = abi.encode( + data.rebalanceId, + data.amount, + data.tokenHash, + data.burnChainId, + data.mintChainId, + data.messageBytes, + data.messageSignature + ); + bytes32 h = ECDSA.toEthSignedMessageHash(keccak256(encoded)); + return verify(h, data.r, data.s, data.v, signer); } } diff --git a/src/library/Utils.sol b/src/library/Utils.sol index 98a6d1f..db38ce3 100644 --- a/src/library/Utils.sol +++ b/src/library/Utils.sol @@ -1,34 +1,30 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.18; +/// @title Utils library +/// @author Orderly_Rubick Orderly_Zion library Utils { - function getAccountId(address addr, string calldata brokerRaw) public pure returns (bytes32 accountId) { - // get brokerId from brokerRaw - bytes32 brokerId = string2HashedBytes32(brokerRaw); - // call `getAccountId(address,bytes32)` - accountId = getAccountId(addr, brokerId); + function getAccountId(address _userAddr, string memory _brokerId) internal pure returns (bytes32) { + return keccak256(abi.encode(_userAddr, calculateStringHash(_brokerId))); } - function getAccountId(address addr, bytes32 brokerId) public pure returns (bytes32 accountId) { - // data is encode addr + brokerId - bytes memory data = abi.encode(addr, brokerId); - // accountId is keccak data - accountId = keccak256(data); + function calculateAccountId(address _userAddr, bytes32 _brokerHash) internal pure returns (bytes32) { + return keccak256(abi.encode(_userAddr, _brokerHash)); } - // string to bytes32, equal to etherjs `ethers.encodeBytes32String('source')` - function string2Bytes32(string memory source) internal pure returns (bytes32 result) { - bytes memory tempEmptyStringTest = bytes(source); - if (tempEmptyStringTest.length == 0) { - return 0x0; - } - assembly { - result := mload(add(source, 32)) - } + function calculateStringHash(string memory _str) internal pure returns (bytes32) { + return keccak256(abi.encodePacked(_str)); } - // string to keccack bytes32 - function string2HashedBytes32(string memory source) public pure returns (bytes32) { - return keccak256(abi.encode(string2Bytes32(source))); + function validateAccountId(bytes32 _accountId, bytes32 _brokerHash, address _userAddress) + internal + pure + returns (bool) + { + return keccak256(abi.encode(_userAddress, _brokerHash)) == _accountId; + } + + function toBytes32(address addr) internal pure returns (bytes32) { + return bytes32(abi.encode(addr)); } } diff --git a/src/library/types/AccountTypes.sol b/src/library/types/AccountTypes.sol index f025427..5ef3dd7 100644 --- a/src/library/types/AccountTypes.sol +++ b/src/library/types/AccountTypes.sol @@ -1,66 +1,105 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.18; -// EnumerableSet -import "openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol"; - +/// @title AccountTypes library +/// @author Orderly_Rubick library AccountTypes { - using EnumerableSet for EnumerableSet.AddressSet; - - int256 constant FUNDING_MOVE_RIGHT_PRECISIONS = 100000000000000000; - bytes32 constant USDC = "USDC"; - struct PerpPosition { - int256 positionQty; - int256 cost_position; - int256 lastSumUnitaryFundings; - uint256 lastExecutedPrice; + int128 positionQty; + int128 costPosition; + int128 lastSumUnitaryFundings; + uint128 lastExecutedPrice; + uint128 lastSettledPrice; + uint128 averageEntryPrice; + int128 openingCost; + uint128 lastAdlPrice; } - // account id, unique for each account, should be accountId -> {Array, brokerId, primaryAddr} - // and keccak256(primaryAddress, brokerID) == accountId + // account id, unique for each account, should be accountId -> {addr, brokerId} + // and keccak256(addr, brokerID) == accountId struct Account { // user's broker id - bytes32 brokerId; - // account addresses - EnumerableSet.AddressSet addresses; + bytes32 brokerHash; // primary address - address primaryAddress; + address userAddress; // mapping symbol => balance - mapping(bytes32 => uint256) balances; - // last perp trade id - uint256 lastPerpTradeId; - // last cefi event id - uint256 lastCefiEventId; + mapping(bytes32 => uint128) balances; + // mapping symbol => totalFrozenBalance + mapping(bytes32 => uint128) totalFrozenBalances; + // mapping withdrawNonce => symbol => balance + mapping(uint64 => mapping(bytes32 => uint128)) frozenBalances; // perp position mapping(bytes32 => PerpPosition) perpPositions; - // reentrancy lock - bool hasPendingSettlementRequest; + // lastwithdraw nonce + uint64 lastWithdrawNonce; + // last perp trade id + uint64 lastPerpTradeId; + // last engine event id + uint64 lastEngineEventId; + // last deposit event id + uint64 lastDepositEventId; } - struct AccountRegister { + struct AccountDeposit { bytes32 accountId; - address addr; - bytes32 brokerId; + bytes32 brokerHash; + address userAddress; + bytes32 tokenHash; + uint256 srcChainId; + uint128 tokenAmount; + uint64 srcChainDepositNonce; } - struct AccountDeposit { + // for accountWithdrawFinish + struct AccountWithdraw { bytes32 accountId; - address addr; - bytes32 symbol; - uint256 amount; + address sender; + address receiver; + bytes32 brokerHash; + bytes32 tokenHash; + uint128 tokenAmount; + uint128 fee; uint256 chainId; + uint64 withdrawNonce; + } + + struct AccountTokenBalances { + // token hash + bytes32 tokenHash; + // balance & frozenBalance + uint128 balance; + uint128 frozenBalance; + } + + struct AccountPerpPositions { + // symbol hash + bytes32 symbolHash; + // perp position + int128 positionQty; + int128 costPosition; + int128 lastSumUnitaryFundings; + uint128 lastExecutedPrice; + uint128 lastSettledPrice; + uint128 averageEntryPrice; + int128 openingCost; + uint128 lastAdlPrice; } - // charge funding fee - function chargeFundingFee(PerpPosition storage position, int256 sumUnitaryFundings) public { - int256 accruedFeeUncoverted = position.positionQty * (sumUnitaryFundings - position.lastSumUnitaryFundings); - int256 accruedFee = accruedFeeUncoverted / FUNDING_MOVE_RIGHT_PRECISIONS; - int256 remainder = accruedFeeUncoverted - (accruedFee * FUNDING_MOVE_RIGHT_PRECISIONS); - if (remainder > 0) { - accruedFee += 1; - } - position.cost_position += accruedFee; - position.lastSumUnitaryFundings = sumUnitaryFundings; + // for batch get + struct AccountSnapshot { + bytes32 accountId; + bytes32 brokerHash; + address userAddress; + uint64 lastWithdrawNonce; + uint64 lastPerpTradeId; + uint64 lastEngineEventId; + uint64 lastDepositEventId; + AccountTokenBalances[] tokenBalances; + AccountPerpPositions[] perpPositions; + } + + struct AccountDelegateSigner { + uint256 chainId; + address signer; } } diff --git a/src/library/types/CrossChainMessageTypes.sol b/src/library/types/CrossChainMessageTypes.sol index 09a5272..683d4b9 100644 --- a/src/library/types/CrossChainMessageTypes.sol +++ b/src/library/types/CrossChainMessageTypes.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.18; +/// @title CrossChainMessageTypes library +/// @author Orderly_Rubick library CrossChainMessageTypes { // The structure of the message struct MessageV1 { @@ -8,7 +10,7 @@ library CrossChainMessageTypes { uint256 dstChainId; // Target blockchain ID bytes32 accountId; // Account address converted to string address addr; // Account address - bytes32 tokenSymbol; // Token symbol for transfer + bytes32 tokenHash; // Token symbol for transfer uint256 tokenAmount; // Amount of token for transfer } } diff --git a/src/library/types/EventTypes.sol b/src/library/types/EventTypes.sol new file mode 100644 index 0000000..cbf4220 --- /dev/null +++ b/src/library/types/EventTypes.sol @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +/// @title EventTypes library +/// @author Orderly_Rubick +library EventTypes { + // EventUpload + struct EventUpload { + EventUploadData[] events; + bytes32 r; + bytes32 s; + uint8 v; + uint8 count; + uint64 batchId; + } + + struct EventUploadData { + uint8 bizType; // 1 - withdraw, 2 - settlement, 3 - adl, 4 - liquidation, 5 - fee distribution, 6 - delegate signer, 7 - delegate withdraw + uint64 eventId; + bytes data; + } + + // WithdrawData + struct WithdrawData { + uint128 tokenAmount; + uint128 fee; + uint256 chainId; // target withdraw chain + bytes32 accountId; + bytes32 r; // String to bytes32, big endian? + bytes32 s; + uint8 v; + address sender; + uint64 withdrawNonce; + address receiver; + uint64 timestamp; + string brokerId; // only this field is string, others should be bytes32 hashedBrokerId + string tokenSymbol; // only this field is string, others should be bytes32 hashedTokenSymbol + } + + struct Settlement { + bytes32 accountId; + bytes32 settledAssetHash; + bytes32 insuranceAccountId; + int128 settledAmount; + uint128 insuranceTransferAmount; + uint64 timestamp; + SettlementExecution[] settlementExecutions; + } + + struct SettlementExecution { + bytes32 symbolHash; + uint128 markPrice; + int128 sumUnitaryFundings; + int128 settledAmount; + } + + struct Adl { + bytes32 accountId; + bytes32 insuranceAccountId; + bytes32 symbolHash; + int128 positionQtyTransfer; + int128 costPositionTransfer; + uint128 adlPrice; + int128 sumUnitaryFundings; + uint64 timestamp; + } + + struct Liquidation { + bytes32 liquidatedAccountId; + bytes32 insuranceAccountId; + bytes32 liquidatedAssetHash; + uint128 insuranceTransferAmount; + uint64 timestamp; + LiquidationTransfer[] liquidationTransfers; + } + + struct LiquidationTransfer { + bytes32 liquidatorAccountId; + bytes32 symbolHash; + int128 positionQtyTransfer; + int128 costPositionTransfer; + int128 liquidatorFee; + int128 insuranceFee; + int128 liquidationFee; + uint128 markPrice; + int128 sumUnitaryFundings; + uint64 liquidationTransferId; + } + + struct FeeDistribution { + bytes32 fromAccountId; + bytes32 toAccountId; + uint128 amount; + bytes32 tokenHash; + } + + struct DelegateSigner { + address delegateSigner; + address delegateContract; + bytes32 brokerHash; + uint256 chainId; + } +} diff --git a/src/library/types/MarketTypes.sol b/src/library/types/MarketTypes.sol new file mode 100644 index 0000000..eb2d834 --- /dev/null +++ b/src/library/types/MarketTypes.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +/// @title MarketTypes library +/// @author Orderly_Rubick +library MarketTypes { + struct PerpMarketCfg { + uint32 baseMaintenanceMargin; + uint32 baseInitialMargin; + uint128 liquidationFeeMax; + uint128 markPrice; + uint128 indexPriceOrderly; + int128 sumUnitaryFundings; + uint256 lastMarkPriceUpdated; + uint256 lastFundingUpdated; + } + + struct UploadPerpPrice { + bytes32 r; + bytes32 s; + uint8 v; + uint64 maxTimestamp; + PerpPrice[] perpPrices; + } + + struct UploadSumUnitaryFundings { + bytes32 r; + bytes32 s; + uint8 v; + uint64 maxTimestamp; + SumUnitaryFunding[] sumUnitaryFundings; + } + + struct PerpPrice { + bytes32 symbolHash; + uint128 indexPrice; + uint128 markPrice; + uint64 timestamp; + } + + struct SumUnitaryFunding { + bytes32 symbolHash; + int128 sumUnitaryFunding; + uint64 timestamp; + } +} diff --git a/src/library/types/OperatorTypes.sol b/src/library/types/OperatorTypes.sol deleted file mode 100644 index 20f7096..0000000 --- a/src/library/types/OperatorTypes.sol +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.18; - -library OperatorTypes { - enum OperatorActionData { - None, - UserRegister, - FuturesTradeUpload, - EventUpload, - PerpMarketInfo - } - - enum CrossChainOperatorActionData { - None, - UserDeposit, - UserEmergencyWithdraw - } -} diff --git a/src/library/types/PerpTypes.sol b/src/library/types/PerpTypes.sol index 381cb1f..bfc835b 100644 --- a/src/library/types/PerpTypes.sol +++ b/src/library/types/PerpTypes.sol @@ -1,84 +1,31 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.18; +/// @title PerpTypes library +/// @author Orderly_Rubick library PerpTypes { // FuturesTradeUploadData struct FuturesTradeUploadData { - uint256 batchId; - uint256 count; + bytes32 r; + bytes32 s; + uint8 v; + uint64 batchId; + uint8 count; FuturesTradeUpload[] trades; } struct FuturesTradeUpload { - uint256 tradeId; - uint256 matchId; bytes32 accountId; - address addr; - string symbol; - bool side; - uint256 tradeQty; - // signature for validate signed by real user - bytes signature; - } - - // EventUpload - struct EventUpload { - uint256 batchId; - uint256 count; - EventUploadData[] events; - } - - struct EventUploadData { - uint256 eventId; - // bytes32 bizType; - // uint256 bizId; - WithdrawData[] withdraws; - Settlement[] settlements; - Liquidation[] liquidations; - uint256[] sequence; - } - - struct WithdrawData { - bytes32 accountId; - address addr; - uint256 amount; - bytes32 symbol; - uint256 chainId; // target withdraw chain - } - - struct Settlement { - bytes32 accountId; - int256 settledAmount; - bytes32 settledAsset; - uint256 insuranceTransferAmount; - SettlementExecution[] settlementExecutions; - } - - struct Liquidation { - bytes32 accountId; - int256 settledAmount; - LiquidationTransfer[] liquidationTransfers; - uint256 timestamp; - bytes32 liquidatedAsset; - } - - struct LiquidationTransfer { - uint256 liquidationTransferId; - bytes32 liquidatorAccountId; - bytes32 listSymbol; - int256 positionQtyTransfer; - int256 costPositionTransfer; - uint256 liquidatorFee; - uint256 insuranceFee; - uint256 markPrice; - int256 sumUnitaryFundings; - uint256 liquidationFee; - } - - struct SettlementExecution { - bytes32 symbol; - int256 sumUnitaryFundings; - uint256 markPrice; - int256 settledAmount; + bytes32 symbolHash; + bytes32 feeAssetHash; + int128 tradeQty; + int128 notional; + uint128 executedPrice; + int128 fee; + int128 sumUnitaryFundings; + uint64 tradeId; + uint64 matchId; + uint64 timestamp; + bool side; // buy (false) or sell (true) } } diff --git a/src/library/types/PerpTypesZip.sol b/src/library/types/PerpTypesZip.sol new file mode 100644 index 0000000..cbd0a2a --- /dev/null +++ b/src/library/types/PerpTypesZip.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +/// @title PerpTypesZip library +/// @author Orderly_Rubick +/// @notice This library is the smaller version of PerpTypes library, used for decompressing calldata size +library PerpTypesZip { + // FuturesTradeUploadDataZip + struct FuturesTradeUploadDataZip { + bytes32 r; + bytes32 s; + uint8 v; + uint64 batchId; + uint8 count; + FuturesTradeUploadZip[] trades; + } + + struct FuturesTradeUploadZip { + bytes32 accountId; + uint8 symbolId; + int128 tradeQty; + uint128 executedPrice; + int128 fee; + int128 sumUnitaryFundings; + uint64 tradeId; + uint64 matchId; + uint64 timestamp; + } +} diff --git a/src/library/types/RebalanceTypes.sol b/src/library/types/RebalanceTypes.sol new file mode 100644 index 0000000..678f300 --- /dev/null +++ b/src/library/types/RebalanceTypes.sol @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +/// @title RebalanceTypes library +/// @author Orderly_Rubick +library RebalanceTypes { + enum RebalanceStatusEnum { + None, + Pending, + Succ, + Fail + } + + // RebalanceStatus + struct RebalanceStatus { + uint64 rebalanceId; // Because the mapping key rebalanceId is mod, so we need to record the real rebalanceId + RebalanceStatusEnum burnStatus; + RebalanceStatusEnum mintStatus; + } + // RebalanceBurnUploadData + + struct RebalanceBurnUploadData { + bytes32 r; + bytes32 s; + uint8 v; + uint64 rebalanceId; + uint128 amount; + bytes32 tokenHash; + uint256 burnChainId; + uint256 mintChainId; + } + + struct RebalanceBurnCCData { + uint32 dstDomain; + uint64 rebalanceId; + uint128 amount; + bytes32 tokenHash; + uint256 burnChainId; + uint256 mintChainId; + address dstVaultAddress; + } + + struct RebalanceBurnCCFinishData { + bool success; + uint64 rebalanceId; + uint128 amount; + bytes32 tokenHash; + uint256 burnChainId; + uint256 mintChainId; + } + + // RebalanceMintUploadData + struct RebalanceMintUploadData { + bytes32 r; + bytes32 s; + uint8 v; + uint64 rebalanceId; + uint128 amount; + bytes32 tokenHash; + uint256 burnChainId; + uint256 mintChainId; + bytes messageBytes; + bytes messageSignature; + } + + struct RebalanceMintCCData { + uint64 rebalanceId; + uint128 amount; + bytes32 tokenHash; + uint256 burnChainId; + uint256 mintChainId; + bytes messageBytes; + bytes messageSignature; + } + + struct RebalanceMintCCFinishData { + bool success; + uint64 rebalanceId; + uint128 amount; + bytes32 tokenHash; + uint256 burnChainId; + uint256 mintChainId; + } +} diff --git a/src/library/types/VaultTypes.sol b/src/library/types/VaultTypes.sol new file mode 100644 index 0000000..4247b04 --- /dev/null +++ b/src/library/types/VaultTypes.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +/// @title VaultTypes library +/// @author Orderly_Rubick +library VaultTypes { + struct VaultDepositFE { + bytes32 accountId; + bytes32 brokerHash; + bytes32 tokenHash; + uint128 tokenAmount; + } + + struct VaultDeposit { + bytes32 accountId; + address userAddress; + bytes32 brokerHash; + bytes32 tokenHash; + uint128 tokenAmount; + uint64 depositNonce; // deposit nonce + } + + struct VaultWithdraw { + bytes32 accountId; + bytes32 brokerHash; + bytes32 tokenHash; + uint128 tokenAmount; + uint128 fee; + address sender; + address receiver; + uint64 withdrawNonce; // withdraw nonce + } + + struct VaultDelegate { + bytes32 brokerHash; + address delegateSigner; + } +} diff --git a/src/library/typesHelper/AccountTypeHelper.sol b/src/library/typesHelper/AccountTypeHelper.sol new file mode 100644 index 0000000..a76a5d3 --- /dev/null +++ b/src/library/typesHelper/AccountTypeHelper.sol @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "../types/AccountTypes.sol"; + +/// @title AccountTypeHelper library +/// @author Orderly_Rubick +library AccountTypeHelper { + error FrozenBalanceInconsistent(); // should never happen + + // ==================== + // part1: methods for get meta data + // ==================== + + /// @notice get balance + function getBalance(AccountTypes.Account storage account, bytes32 tokenHash) internal view returns (uint128) { + return account.balances[tokenHash]; + } + + /// @notice get brokerHash + function getBrokerHash(AccountTypes.Account storage account) internal view returns (bytes32) { + return account.brokerHash; + } + + /// @notice get last engine event id + function getLastEngineEventId(AccountTypes.Account storage account) internal view returns (uint64) { + return account.lastEngineEventId; + } + + // ==================== + // part2: methods for balance | frozen balance + // ==================== + + /// @notice add balance + function addBalance(AccountTypes.Account storage account, bytes32 tokenHash, uint128 amount) internal { + account.balances[tokenHash] += amount; + } + + /// @notice sub balance + function subBalance(AccountTypes.Account storage account, bytes32 tokenHash, uint128 amount) internal { + account.balances[tokenHash] -= amount; + } + + /// @notice frozen balance with a given withdrawNonce & amount + function frozenBalance( + AccountTypes.Account storage account, + uint64 withdrawNonce, + bytes32 tokenHash, + uint128 amount + ) internal { + account.balances[tokenHash] -= amount; + account.totalFrozenBalances[tokenHash] += amount; + account.frozenBalances[withdrawNonce][tokenHash] = amount; + account.lastWithdrawNonce = withdrawNonce; + } + + /// @notice revert frozen balance + function unfrozenBalance( + AccountTypes.Account storage account, + uint64 withdrawNonce, + bytes32 tokenHash, + uint128 amount + ) internal { + account.balances[tokenHash] += amount; + account.totalFrozenBalances[tokenHash] -= amount; + account.frozenBalances[withdrawNonce][tokenHash] -= amount; + if (account.frozenBalances[withdrawNonce][tokenHash] != 0) revert FrozenBalanceInconsistent(); + } + + /// @notice transfer frozen balance out + function finishFrozenBalance( + AccountTypes.Account storage account, + uint64 withdrawNonce, + bytes32 tokenHash, + uint128 amount + ) internal { + account.totalFrozenBalances[tokenHash] -= amount; + account.frozenBalances[withdrawNonce][tokenHash] -= amount; + if (account.frozenBalances[withdrawNonce][tokenHash] != 0) revert FrozenBalanceInconsistent(); + } + + function getFrozenTotalBalance(AccountTypes.Account storage account, bytes32 tokenHash) + internal + view + returns (uint128) + { + return account.totalFrozenBalances[tokenHash]; + } + + function getFrozenWithdrawNonceBalance( + AccountTypes.Account storage account, + uint64 withdrawNonce, + bytes32 tokenHash + ) internal view returns (uint128) { + return account.frozenBalances[withdrawNonce][tokenHash]; + } +} diff --git a/src/library/typesHelper/AccountTypePositionHelper.sol b/src/library/typesHelper/AccountTypePositionHelper.sol new file mode 100644 index 0000000..1cdd251 --- /dev/null +++ b/src/library/typesHelper/AccountTypePositionHelper.sol @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "openzeppelin-contracts/contracts/utils/math/SafeCast.sol"; +import "../types/AccountTypes.sol"; +import "../Utils.sol"; +import "./SafeCastHelper.sol"; + +/// @title AccountTypePositionHelper library +/// @author Orderly_Rubick +library AccountTypePositionHelper { + using SafeCastHelper for *; + + int128 constant FUNDING_MOVE_RIGHT_PRECISIONS = 1e17; // 1e17 + int128 constant PRICE_QTY_MOVE_RIGHT_PRECISIONS = 1e10; // 1e10 + int32 constant MARGIN_100PERCENT = 1e4; // 1e4 + + /// @notice charge funding fee + function chargeFundingFee(AccountTypes.PerpPosition storage position, int128 sumUnitaryFundings) internal { + int128 accruedFeeUnconverted = position.positionQty * (sumUnitaryFundings - position.lastSumUnitaryFundings); + int128 accruedFee = accruedFeeUnconverted / FUNDING_MOVE_RIGHT_PRECISIONS; + int128 remainder = accruedFeeUnconverted - (accruedFee * FUNDING_MOVE_RIGHT_PRECISIONS); + if (remainder > 0) { + accruedFee += 1; + } + position.costPosition += accruedFee; + position.lastSumUnitaryFundings = sumUnitaryFundings; + } + + /// @notice cal pnl + function calPnl(AccountTypes.PerpPosition storage position, int128 markPrice) internal view returns (int128) { + return position.positionQty * markPrice / FUNDING_MOVE_RIGHT_PRECISIONS - position.costPosition; + } + + /// @notice maintenance margin + function maintenanceMargin( + AccountTypes.PerpPosition storage position, + int128 markPrice, + int128 baseMaintenanceMargin + ) internal view returns (int128) { + return position.positionQty.abs().toInt128() * markPrice * baseMaintenanceMargin + / (int128(MARGIN_100PERCENT) * PRICE_QTY_MOVE_RIGHT_PRECISIONS); + } + + /// @notice is full settled + function isFullSettled(AccountTypes.PerpPosition storage position) internal view returns (bool) { + return position.positionQty == 0 && position.costPosition == 0; + } + + /// @notice only change averageEntryPrice, openingCost + /// params: + /// qty: decimal is 8 + /// price: decimal is 8 + /// liquidationQuoteDiff: decimal is 6 + /// for: + /// perp trade: liquidationQuoteDiff should be empty + /// liquidation: liquidationQuoteDiff should no be empty + function calAverageEntryPrice( + AccountTypes.PerpPosition storage position, + int128 qty, + int128 price, + int128 liquidationQuoteDiff + ) internal { + if (qty == 0) { + return; + } + int128 currentHolding = position.positionQty + qty; + if (currentHolding == 0) { + position.averageEntryPrice = 0; + position.openingCost = 0; + return; + } + // precision 16 = 6 + 10 + int128 quoteDiff = liquidationQuoteDiff != 0 ? liquidationQuoteDiff * 1e10 : -qty * price; + // precision 16 = 8 + 8 + int128 openingCost = position.openingCost * 1e8; + if (position.positionQty * currentHolding > 0) { + if (qty * position.positionQty > 0) { + openingCost += quoteDiff; + } else { + int128 v = halfUp24_8_i256(int256(openingCost) * int256(qty), position.positionQty); + openingCost += v; + } + } else { + openingCost = halfUp24_8_i256(int256(quoteDiff) * int256(currentHolding), qty); + } + if (currentHolding > 0) { + position.averageEntryPrice = halfDown16_8(-openingCost, currentHolding).toUint128(); + } else { + position.averageEntryPrice = halfUp16_8(-openingCost, currentHolding).toUint128(); + } + position.openingCost = halfUp16_8(openingCost, 1e8); + } + + /// @notice dividend has move right 24 precisions, divisor move right 8 + function halfUp24_8(int128 dividend, int128 divisor) internal pure returns (int128) { + // to eliminate effects of dividend extra move right 8 precision in outer + return halfUp16_8(dividend, divisor * 1e8) * 1e8; + } + + function halfUp24_8_i256(int256 dividend, int128 divisor) internal pure returns (int128) { + // to eliminate effects of dividend extra move right 8 precision in outer + return halfUp16_8_i256(dividend, divisor * 1e8) * 1e8; + } + + /// @notice HALF UP + /// Rounding mode to round towards "nearest neighbor" + /// unless both neighbors are equidistant, in which case round up. + /// Behaves as for RoundingMode UP if the discarded + /// fraction is >=0.5; + /// half up + /// 5.5 -> 6 + /// 2.5 -> 3 + /// 1.6 -> 2 + /// 1.1 -> 1 + /// 1.0 -> 1 + /// -1.0 -> -1 + /// -1.1 -> -1 + /// -1.6 -> -2 + /// -2.5 -> -3 + /// -5.5 -> -6 + function halfUp16_8(int128 dividend, int128 divisor) internal pure returns (int128) { + int128 quotient = dividend / divisor; + int128 remainder = dividend % divisor; + if (remainder.abs() * 2 >= divisor.abs()) { + if (quotient > 0) { + quotient += 1; + } else if (quotient < 0) { + quotient -= 1; + } else { + // case quotient == 0 + if (dividend > 0) { + quotient = 1; + } else { + quotient = -1; + } + } + } + return quotient; + } + + function halfUp16_8_i256(int256 dividend, int128 divisor) internal pure returns (int128) { + int256 quotient = dividend / divisor; + int256 remainder = dividend % divisor; + if (remainder.abs_i256() * 2 >= divisor.abs()) { + if (quotient >= 0) { + quotient += 1; + } else if (quotient < 0) { + quotient -= 1; + } else { + // case quotient == 0 + if (dividend > 0) { + quotient = 1; + } else { + quotient = -1; + } + } + } + return SafeCast.toInt128(quotient); + } + + /// @notice HALF DOWN + /// Rounding mode to round towards "nearest neighbor" + /// unless both neighbors are equidistant, in which case round + /// down. Behaves as for RoundingMode UP if the discarded + /// fraction is > 0.5; + /// Example: + /// 5.5 -> 5 + /// 2.5 -> 2 + /// 1.6 -> 2 + /// 1.1 -> 1 + /// 1.0 -> 1 + /// -1.0 -> -1 + /// -1.1 -> -1 + /// -1.6 -> -2 + /// -2.5 -> -2 + /// -5.5 -> -5 + function halfDown16_8(int128 dividend, int128 divisor) internal pure returns (int128) { + int128 quotient = dividend / divisor; + int128 remainder = dividend % divisor; + if (remainder.abs() * 2 > divisor.abs()) { + if (quotient > 0) { + quotient += 1; + } else if (quotient < 0) { + quotient -= 1; + } else { + // case quotient == 0 + if (dividend > 0) { + quotient = 1; + } else { + quotient = -1; + } + } + } + return quotient; + } +} diff --git a/src/library/typesHelper/MarketTypeHelper.sol b/src/library/typesHelper/MarketTypeHelper.sol new file mode 100644 index 0000000..e6f4734 --- /dev/null +++ b/src/library/typesHelper/MarketTypeHelper.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "../types/MarketTypes.sol"; + +/// @title MarketTypeHelper library +/// @author Orderly_Rubick +library MarketTypeHelper { + function setBaseMaintenanceMargin(MarketTypes.PerpMarketCfg storage _perpMarketCfg, uint32 _baseMaintenanceMargin) + internal + { + _perpMarketCfg.baseMaintenanceMargin = _baseMaintenanceMargin; + } + + function setBaseInitialMargin(MarketTypes.PerpMarketCfg storage _perpMarketCfg, uint32 _baseInitialMargin) + internal + { + _perpMarketCfg.baseInitialMargin = _baseInitialMargin; + } + + function setLiquidationFeeMax(MarketTypes.PerpMarketCfg storage _perpMarketCfg, uint128 _liquidationFeeMax) + internal + { + _perpMarketCfg.liquidationFeeMax = _liquidationFeeMax; + } + + function setMarkPrice(MarketTypes.PerpMarketCfg storage _perpMarketCfg, uint128 _markPrice) internal { + _perpMarketCfg.markPrice = _markPrice; + } + + function setIndexPriceOrderly(MarketTypes.PerpMarketCfg storage _perpMarketCfg, uint128 _indexPriceOrderly) + internal + { + _perpMarketCfg.indexPriceOrderly = _indexPriceOrderly; + } + + function setSumUnitaryFundings(MarketTypes.PerpMarketCfg storage _perpMarketCfg, int128 _sumUnitaryFundings) + internal + { + _perpMarketCfg.sumUnitaryFundings = _sumUnitaryFundings; + } + + function setLastMarkPriceUpdated(MarketTypes.PerpMarketCfg storage _perpMarketCfg, uint256 _lastMarkPriceUpdated) + internal + { + _perpMarketCfg.lastMarkPriceUpdated = _lastMarkPriceUpdated; + } + + function setLastFundingUpdated(MarketTypes.PerpMarketCfg storage _perpMarketCfg, uint256 _lastFundingUpdated) + internal + { + _perpMarketCfg.lastFundingUpdated = _lastFundingUpdated; + } +} diff --git a/src/library/typesHelper/SafeCastHelper.sol b/src/library/typesHelper/SafeCastHelper.sol new file mode 100644 index 0000000..860f802 --- /dev/null +++ b/src/library/typesHelper/SafeCastHelper.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +/// @title SafeCastHelper library +/// @author Orderly_Rubick +/// @notice For safe cast uint types +library SafeCastHelper { + error SafeCastOverflow(); + error SafeCastUnderflow(); + + /// @notice cast uint128 to int128 + function toInt128(uint128 y) internal pure returns (int128 z) { + if (y > uint128(type(int128).max)) revert SafeCastOverflow(); + z = int128(y); + } + + /// @notice cast int128 to uint128 + function toUint128(int128 y) internal pure returns (uint128 z) { + if (y < 0) revert SafeCastUnderflow(); + z = uint128(y); + } + + /// @notice safe abs 256 + function abs(int128 x) internal pure returns (uint128 y) { + if (x == type(int128).min) { + y = 1 << 127; + } else { + y = uint128(x < 0 ? -x : x); + } + } + + /// @notice safe abs int256 + function abs_i256(int256 x) internal pure returns (uint256 y) { + if (x == type(int256).min) { + y = 1 << 255; + } else { + y = uint256(x < 0 ? -x : x); + } + } +} diff --git a/src/vaultSide/Vault.sol b/src/vaultSide/Vault.sol new file mode 100644 index 0000000..f7bf8af --- /dev/null +++ b/src/vaultSide/Vault.sol @@ -0,0 +1,338 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "../interface/IVault.sol"; +import "../interface/IVaultCrossChainManager.sol"; +import "../library/Utils.sol"; +import "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; +import "openzeppelin-contracts-upgradeable/contracts/security/PausableUpgradeable.sol"; +import "openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol"; +import "openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol"; +import "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol"; +import "../interface/cctp/ITokenMessenger.sol"; +import "../interface/cctp/IMessageTransmitter.sol"; + +/// @title Vault contract +/// @author Orderly_Rubick, Orderly_Zion +/// @notice Vault is responsible for saving user's erc20 token. +/// EACH CHAIN SHOULD HAVE ONE Vault CONTRACT. +/// User can deposit erc20 (USDC) from Vault. +/// Only crossChainManager can approve withdraw request. +contract Vault is IVault, PausableUpgradeable, OwnableUpgradeable { + using EnumerableSet for EnumerableSet.Bytes32Set; + using SafeERC20 for IERC20; + + // The cross-chain manager address on Vault side + address public crossChainManagerAddress; + // An incrasing deposit id / nonce on Vault side + uint64 public depositId; + + // A set to record the hash value of all allowed brokerIds // brokerHash = keccak256(abi.encodePacked(brokerId)) + EnumerableSet.Bytes32Set private allowedBrokerSet; + // A set to record the hash value of all allowed tokens // tokenHash = keccak256(abi.encodePacked(tokenSymbol)) + EnumerableSet.Bytes32Set private allowedTokenSet; + // A mapping from tokenHash to token contract address + mapping(bytes32 => address) public allowedToken; + // A flag to indicate if deposit fee is enabled + bool public depositFeeEnabled; + + // https://developers.circle.com/stablecoin/docs/cctp-protocol-contract#tokenmessenger-mainnet + // TokenMessager for CCTP + address public tokenMessengerContract; + // MessageTransmitterContract for CCTP + address public messageTransmitterContract; + + /// @notice Require only cross-chain manager can call + modifier onlyCrossChainManager() { + if (msg.sender != crossChainManagerAddress) revert OnlyCrossChainManagerCanCall(); + _; + } + + /// @notice check non-zero address + modifier nonZeroAddress(address _address) { + if (_address == address(0)) revert AddressZero(); + _; + } + + constructor() { + _disableInitializers(); + } + + function initialize() external override initializer { + __Ownable_init(); + __Pausable_init(); + } + + /// @notice Change crossChainManager address + function setCrossChainManager(address _crossChainManagerAddress) + public + override + onlyOwner + nonZeroAddress(_crossChainManagerAddress) + { + emit ChangeCrossChainManager(crossChainManagerAddress, _crossChainManagerAddress); + crossChainManagerAddress = _crossChainManagerAddress; + } + + /// @notice Add contract address for an allowed token given the tokenHash + /// @dev This function is only called when changing allow status for a token, not for initializing + function setAllowedToken(bytes32 _tokenHash, bool _allowed) public override onlyOwner { + bool succ = false; + if (_allowed) { + // require tokenAddress exist + if (allowedToken[_tokenHash] == address(0)) revert AddressZero(); + succ = allowedTokenSet.add(_tokenHash); + } else { + succ = allowedTokenSet.remove(_tokenHash); + } + if (!succ) revert EnumerableSetError(); + emit SetAllowedToken(_tokenHash, _allowed); + } + + /// @notice Add the hash value for an allowed brokerId + function setAllowedBroker(bytes32 _brokerHash, bool _allowed) public override onlyOwner { + bool succ = false; + if (_allowed) { + succ = allowedBrokerSet.add(_brokerHash); + } else { + succ = allowedBrokerSet.remove(_brokerHash); + } + if (!succ) revert EnumerableSetError(); + emit SetAllowedBroker(_brokerHash, _allowed); + } + + /// @notice Change the token address for an allowed token, used when a new token is added + /// @dev maybe should called `addTokenAddressAndAllow`, because it's for initializing + function changeTokenAddressAndAllow(bytes32 _tokenHash, address _tokenAddress) + public + override + onlyOwner + nonZeroAddress(_tokenAddress) + { + allowedToken[_tokenHash] = _tokenAddress; + allowedTokenSet.add(_tokenHash); // ignore returns here + emit ChangeTokenAddressAndAllow(_tokenHash, _tokenAddress); + } + + /// @notice Check if the given tokenHash is allowed on this Vault + function getAllowedToken(bytes32 _tokenHash) public view override returns (address) { + if (allowedTokenSet.contains(_tokenHash)) { + return allowedToken[_tokenHash]; + } else { + return address(0); + } + } + + /// @notice Check if the brokerHash is allowed on this Vault + function getAllowedBroker(bytes32 _brokerHash) public view override returns (bool) { + return allowedBrokerSet.contains(_brokerHash); + } + + /// @notice Get all allowed tokenHash from this Vault + function getAllAllowedToken() public view override returns (bytes32[] memory) { + return allowedTokenSet.values(); + } + + /// @notice Get all allowed brokerIds hash from this Vault + function getAllAllowedBroker() public view override returns (bytes32[] memory) { + return allowedBrokerSet.values(); + } + + /// @notice The function to receive user deposit, VaultDepositFE type is defined in VaultTypes.sol + function deposit(VaultTypes.VaultDepositFE calldata data) public payable override whenNotPaused { + _deposit(msg.sender, data); + } + + /// @notice The function to allow users to deposit on behalf of another user, the receiver is the user who will receive the deposit + function depositTo(address receiver, VaultTypes.VaultDepositFE calldata data) + public + payable + override + whenNotPaused + { + _deposit(receiver, data); + } + + /// @notice The function to query layerzero fee from CrossChainManager contract + function getDepositFee(address receiver, VaultTypes.VaultDepositFE calldata data) + public + view + override + whenNotPaused + returns (uint256) + { + _validateDeposit(receiver, data); + VaultTypes.VaultDeposit memory depositData = VaultTypes.VaultDeposit( + data.accountId, receiver, data.brokerHash, data.tokenHash, data.tokenAmount, depositId + 1 + ); + return (IVaultCrossChainManager(crossChainManagerAddress).getDepositFee(depositData)); + } + + /// @notice The function to enable/disable deposit fee + function enableDepositFee(bool _enabled) public override onlyOwner whenNotPaused { + depositFeeEnabled = _enabled; + } + + /// @notice The function to call deposit of CCManager contract + function _deposit(address receiver, VaultTypes.VaultDepositFE calldata data) internal whenNotPaused { + _validateDeposit(receiver, data); + // avoid reentrancy, so `transferFrom` token at the beginning + IERC20 tokenAddress = IERC20(allowedToken[data.tokenHash]); + // avoid non-standard ERC20 tranferFrom bug + tokenAddress.safeTransferFrom(msg.sender, address(this), data.tokenAmount); + // cross-chain tx to ledger + VaultTypes.VaultDeposit memory depositData = VaultTypes.VaultDeposit( + data.accountId, receiver, data.brokerHash, data.tokenHash, data.tokenAmount, _newDepositId() + ); + // if deposit fee is enabled, user should pay fee in native token and the msg.value will be forwarded to CrossChainManager to pay for the layerzero cross-chain fee + if (depositFeeEnabled) { + if (msg.value == 0) revert ZeroDepositFee(); + IVaultCrossChainManager(crossChainManagerAddress).depositWithFee{value: msg.value}(depositData); + } else { + IVaultCrossChainManager(crossChainManagerAddress).deposit(depositData); + } + emit AccountDepositTo(data.accountId, receiver, depositId, data.tokenHash, data.tokenAmount); + } + + /// @notice The function to validate deposit data + function _validateDeposit(address receiver, VaultTypes.VaultDepositFE calldata data) internal view { + // check if tokenHash and brokerHash are allowed + if (!allowedTokenSet.contains(data.tokenHash)) revert TokenNotAllowed(); + if (!allowedBrokerSet.contains(data.brokerHash)) revert BrokerNotAllowed(); + // check if accountId = keccak256(abi.encodePacked(brokerHash, receiver)) + if (!Utils.validateAccountId(data.accountId, data.brokerHash, receiver)) revert AccountIdInvalid(); + // check if tokenAmount > 0 + if (data.tokenAmount == 0) revert ZeroDeposit(); + } + + /// @notice user withdraw + function withdraw(VaultTypes.VaultWithdraw calldata data) public override onlyCrossChainManager whenNotPaused { + // send cross-chain tx to ledger + IVaultCrossChainManager(crossChainManagerAddress).withdraw(data); + // avoid reentrancy, so `transfer` token at the end + IERC20 tokenAddress = IERC20(allowedToken[data.tokenHash]); + uint128 amount = data.tokenAmount - data.fee; + // avoid revert if transfer to zero address. + /// @notice This check condition should always be true because cc promise that + if (data.receiver != address(0)) { + // avoid non-standard ERC20 tranfer bug + tokenAddress.safeTransfer(data.receiver, amount); + } + // emit withdraw event + emit AccountWithdraw( + data.accountId, + data.withdrawNonce, + data.brokerHash, + data.sender, + data.receiver, + data.tokenHash, + data.tokenAmount, + data.fee + ); + } + + function delegateSigner(VaultTypes.VaultDelegate calldata data) public override { + if ((msg.sender).code.length == 0) revert ZeroCodeLength(); + if ((data.delegateSigner).code.length != 0) revert NotZeroCodeLength(); + if (!allowedBrokerSet.contains(data.brokerHash)) revert BrokerNotAllowed(); + + // emit delegate event + emit AccountDelegate(msg.sender, data.brokerHash, data.delegateSigner, block.chainid, block.number); + } + + /// @notice Update the depositId + function _newDepositId() internal returns (uint64) { + return ++depositId; + } + + function emergencyPause() public whenNotPaused onlyOwner { + _pause(); + } + + function emergencyUnpause() public whenPaused onlyOwner { + _unpause(); + } + + function setTokenMessengerContract(address _tokenMessengerContract) + public + override + onlyOwner + nonZeroAddress(_tokenMessengerContract) + { + tokenMessengerContract = _tokenMessengerContract; + } + + function setRebalanceMessengerContract(address _rebalanceMessengerContract) + public + override + onlyOwner + nonZeroAddress(_rebalanceMessengerContract) + { + messageTransmitterContract = _rebalanceMessengerContract; + } + + function rebalanceBurn(RebalanceTypes.RebalanceBurnCCData calldata data) external override onlyCrossChainManager { + address burnToken = allowedToken[data.tokenHash]; + if (burnToken == address(0)) revert AddressZero(); + IERC20(burnToken).approve(tokenMessengerContract, data.amount); + try ITokenMessenger(tokenMessengerContract).depositForBurn( + data.amount, data.dstDomain, Utils.toBytes32(data.dstVaultAddress), burnToken + ) { + // send succ cross-chain tx to ledger + // rebalanceId, amount, tokenHash, burnChainId, mintChainId | true + IVaultCrossChainManager(crossChainManagerAddress).burnFinish( + RebalanceTypes.RebalanceBurnCCFinishData({ + rebalanceId: data.rebalanceId, + amount: data.amount, + tokenHash: data.tokenHash, + burnChainId: data.burnChainId, + mintChainId: data.mintChainId, + success: true + }) + ); + } catch { + // send fail cross-chain tx to ledger + // rebalanceId, amount, tokenHash, burnChainId, mintChainId | false + IVaultCrossChainManager(crossChainManagerAddress).burnFinish( + RebalanceTypes.RebalanceBurnCCFinishData({ + rebalanceId: data.rebalanceId, + amount: data.amount, + tokenHash: data.tokenHash, + burnChainId: data.burnChainId, + mintChainId: data.mintChainId, + success: false + }) + ); + } + } + + function rebalanceMint(RebalanceTypes.RebalanceMintCCData calldata data) external override onlyCrossChainManager { + try IMessageTransmitter(messageTransmitterContract).receiveMessage(data.messageBytes, data.messageSignature) { + // send succ cross-chain tx to ledger + // rebalanceId, amount, tokenHash, burnChainId, mintChainId | true + IVaultCrossChainManager(crossChainManagerAddress).mintFinish( + RebalanceTypes.RebalanceMintCCFinishData({ + rebalanceId: data.rebalanceId, + amount: data.amount, + tokenHash: data.tokenHash, + burnChainId: data.burnChainId, + mintChainId: data.mintChainId, + success: true + }) + ); + } catch { + // send fail cross-chain tx to ledger + // rebalanceId, amount, tokenHash, burnChainId, mintChainId | false + IVaultCrossChainManager(crossChainManagerAddress).mintFinish( + RebalanceTypes.RebalanceMintCCFinishData({ + rebalanceId: data.rebalanceId, + amount: data.amount, + tokenHash: data.tokenHash, + burnChainId: data.burnChainId, + mintChainId: data.mintChainId, + success: false + }) + ); + } + } +} diff --git a/src/testUSDC/tUSDC.sol b/src/vaultSide/tUSDC.sol similarity index 100% rename from src/testUSDC/tUSDC.sol rename to src/vaultSide/tUSDC.sol diff --git a/src/zip/DecompressorExtension.sol b/src/zip/DecompressorExtension.sol new file mode 100644 index 0000000..4819200 --- /dev/null +++ b/src/zip/DecompressorExtension.sol @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.18; + +/** + * @title DecompressorExtension + * @dev A contract that implements a decompression algorithm to be used in conjunction with compressed data. + * You should implement in your contract a function that makes use of the internal methods `_setData`, `_setDataArray` for data addition to the dictionary. + * NOTE: It is important to implement a delay when using the `_setData` and `_setDataArray` methods in your transactions. This delay helps to guard against the possibility of frontrunning, which can occur when the state of the dictionary changes during the execution of a transaction. + * @notice This extension could result in a much higher gas consumption than expected and could potentially lead to significant memory expansion costs. Be sure to properly estimate these aspects to avoid unforeseen expenses. + */ +abstract contract DecompressorExtension { + /** + * @dev Emitted when an offset is used incorrectly, either because it is too small, or because its sum with a dict data's length exceeds a certain limit. + * @param value The incorrect value used as an offset or as the sum of the offset and a dict data's length. + */ + error IncorrectDictAccess(uint256 value); + + uint256 public constant MAX_DICT_LEN = 1_048_576; // 2 ** 20 + uint256 public constant RESERVE_DICT_LEN = 2; // 0: msg.sender; 1: address(this) + + /** + * @dev The dictionary mapping storage slots to their associated compressed data. + */ + bytes32[MAX_DICT_LEN] private _dict; + + /** + * @dev Ensures the provided value is correctly used as an offset. This includes checks for the offset being too small or its sum with an dict data's length exceeding a certain limit. Value less `RESERVE_DICT_LEN` are reserved. + * @param value The value used as an offset or as the sum of the offset and an array's length. + */ + modifier validDictAccess(uint256 value) { + if (value < RESERVE_DICT_LEN || value >= MAX_DICT_LEN) revert IncorrectDictAccess(value); + _; + } + + /** + * @dev Returns the data stored in the dictionary in the specified range. + * @param begin The starting index of the data range to return. First 2 positions are reserved, so it should be greater than 1. + * @param end The ending index of the data range to return. + * @return res An array of bytes32 values containing the data in the specified range. + */ + function getData(uint256 begin, uint256 end) + external + view + validDictAccess(begin) + validDictAccess(end) + returns (bytes32[] memory res) + { + unchecked { + if (begin < end) { + res = new bytes32[](end - begin); + for (uint256 i = begin; i < end; i++) { + res[i - begin] = _dict[i]; + } + } + } + } + + /** + * @dev Sets the data at the specified dictionary offset. + * @param offset The dictionary offset to set the data at. First 2 positions are reserved, so it should be greater than 1. + * @param data The data to be stored at the specified offset. + */ + function _setData(uint256 offset, bytes32 data) internal validDictAccess(offset) { + unchecked { + _dict[offset] = data; + } + } + + /** + * @dev Sets an array of data starting at the specified dictionary offset. + * @param offset The starting dictionary offset to set the data at. First 2 positions are reserved, so it should be greater than 1. + * @param dataArray The array of data to be stored starting at the specified offset. + */ + function _setDataArray(uint256 offset, bytes32[] calldata dataArray) + internal + validDictAccess(offset) + validDictAccess(offset + dataArray.length) + { + unchecked { + for (uint256 i = 0; i < dataArray.length; i++) { + _dict[offset + i] = dataArray[i]; + } + } + } + + /** + * @dev Decompresses the compressed data (N bytes) passed to the function using the _delegatecall function. + */ + function decompress(bytes calldata data) external payable { + _delegatecall(decompressed(data)); + } + + /** + * @dev Calculates and returns the decompressed data from the compressed calldata. + * @return raw The decompressed raw data. + */ + function decompressed(bytes calldata data) public view returns (bytes memory raw) { + return _decompressed(data); + } + + /** + * @dev Calculates and returns the decompressed raw data from the compressed data passed as an argument. + * @param cd The compressed data to be decompressed. + * @return raw The decompressed raw data. + */ + function _decompressed(bytes calldata cd) internal view returns (bytes memory raw) { + assembly ("memory-safe") { + // solhint-disable-line no-inline-assembly + raw := mload(0x40) + let outptr := add(raw, 0x20) + let end := add(cd.offset, cd.length) + for { let inptr := cd.offset } lt(inptr, end) {} { + // solhint-disable-line no-empty-blocks + let data := calldataload(inptr) + + let key + + // 00XXXXXX - insert X+1 zero bytes + // 01PXXXXX - copy X+1 bytes calldata (P means padding to 32 bytes or not) + // 10BBXXXX XXXXXXXX - use 12 bits as key for [32,20,4,31][B] bytes from storage X + // 11BBXXXX XXXXXXXX XXXXXXXX - use 20 bits as [32,20,4,31][B] bytes from storage X + switch shr(254, data) + case 0 { + let size := add(byte(0, data), 1) + calldatacopy(outptr, calldatasize(), size) + inptr := add(inptr, 1) + outptr := add(outptr, size) + continue + } + case 1 { + let size := add(and(0x1F, byte(0, data)), 1) + if and(data, 0x2000000000000000000000000000000000000000000000000000000000000000) { + mstore(outptr, 0) + outptr := add(outptr, sub(32, size)) + } + calldatacopy(outptr, add(inptr, 1), size) + inptr := add(inptr, add(1, size)) + outptr := add(outptr, size) + continue + } + case 2 { + key := shr(244, shl(4, data)) + inptr := add(inptr, 2) + // fallthrough + } + case 3 { + key := shr(236, shl(4, data)) + inptr := add(inptr, 3) + // fallthrough + } + + // TODO: check sload argument + let value + switch key + case 0 { value := caller() } + case 1 { value := address() } + default { value := sload(add(_dict.slot, key)) } + + switch shr(254, shl(2, data)) + case 0 { + mstore(outptr, value) + outptr := add(outptr, 32) + } + case 1 { + mstore(outptr, shl(96, value)) + outptr := add(outptr, 20) + } + case 2 { + mstore(outptr, shl(224, value)) + outptr := add(outptr, 4) + } + default { + mstore(outptr, shl(8, value)) + outptr := add(outptr, 31) + } + } + mstore(raw, sub(sub(outptr, raw), 0x20)) + mstore(0x40, outptr) + } + } + + /** + * @dev Executes a delegate call to the raw data calculated by the _decompressed function. + * @param raw The raw data to execute the delegate call with. + */ + function _delegatecall(bytes memory raw) internal { + assembly ("memory-safe") { + // solhint-disable-line no-inline-assembly + let success := delegatecall(gas(), address(), add(raw, 0x20), mload(raw), 0, 0) + returndatacopy(0, 0, returndatasize()) + if success { return(0, returndatasize()) } + revert(0, returndatasize()) + } + } +} diff --git a/src/zip/OperatorManagerZip.sol b/src/zip/OperatorManagerZip.sol new file mode 100644 index 0000000..a373bb3 --- /dev/null +++ b/src/zip/OperatorManagerZip.sol @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.18; + +import "openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol"; +import "./DecompressorExtension.sol"; +import "../interface/IOperatorManagerZip.sol"; +import "../dataLayout/OperatorManagerZipDataLayout.sol"; +import "../interface/IOperatorManager.sol"; + +/// @title Operator call this contract to decompress calldata +/// @author Orderly_Zion, Orderly_Rubick +/// @notice OperatorManagerZip is responsible for decompressing calldata size to save L1 gas, only called by operator. +/// @notice This contract could be deprecated after Cancun upgrade +contract OperatorManagerZip is + OwnableUpgradeable, + DecompressorExtension, + IOperatorManagerZip, + OperatorManagerZipDataLayout +{ + constructor() { + _disableInitializers(); + } + + modifier onlyOperator() { + if (msg.sender != zipOperatorAddress) revert OnlyOperatorCanCall(); + _; + } + + /// @notice check non-zero address + modifier nonZeroAddress(address _address) { + if (_address == address(0)) revert AddressZero(); + _; + } + + function initialize() external override initializer { + __Ownable_init(); + } + + function setOperator(address _operatorAddress) external override onlyOwner nonZeroAddress(_operatorAddress) { + emit ChangeOperator(zipOperatorAddress, _operatorAddress); + zipOperatorAddress = _operatorAddress; + } + + function setOpeartorManager(address _operatorManager) + external + override + onlyOwner + nonZeroAddress(_operatorManager) + { + emit ChangeOperatorManager(address(operatorManager), _operatorManager); + operatorManager = IOperatorManager(_operatorManager); + } + + function setSymbol(bytes32 symbolHash, uint8 symbolId) external override onlyOwner { + symbolId2Hash[symbolId] = symbolHash; + } + + function decodeFuturesTradeUploadData(bytes calldata data) external override onlyOperator { + bytes memory raw = _decompressed(data); + PerpTypesZip.FuturesTradeUploadDataZip memory decoded = + abi.decode(raw, (PerpTypesZip.FuturesTradeUploadDataZip)); + PerpTypes.FuturesTradeUploadData memory decodedData = PerpTypes.FuturesTradeUploadData({ + r: decoded.r, + s: decoded.s, + v: decoded.v, + batchId: decoded.batchId, + count: decoded.count, + trades: new PerpTypes.FuturesTradeUpload[](decoded.count) + }); + for (uint8 i = 0; i < decoded.count; i++) { + PerpTypesZip.FuturesTradeUploadZip memory zipData = decoded.trades[i]; + if (symbolId2Hash[zipData.symbolId] == 0x0) revert SymbolNotRegister(); + // notional = tradeQty * executedPrice / 1e10, where tradeQty is int128, executedPrice is uint128 + // no worry about overflow, we expand the notional to int256 + decodedData.trades[i] = PerpTypes.FuturesTradeUpload({ + accountId: zipData.accountId, + symbolHash: symbolId2Hash[zipData.symbolId], + feeAssetHash: 0xd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa, // hash of "USDC" + tradeQty: zipData.tradeQty, + notional: calcNotional(zipData.tradeQty, zipData.executedPrice), + executedPrice: zipData.executedPrice, + fee: zipData.fee, + sumUnitaryFundings: zipData.sumUnitaryFundings, + tradeId: zipData.tradeId, + matchId: zipData.matchId, + timestamp: zipData.timestamp, + side: zipData.tradeQty < 0 // buy (false) or sell (true) + }); + } + IOperatorManager(operatorManager).futuresTradeUpload(decodedData); + } + + function initSymbolId2Hash() external override onlyOwner { + // https://wootraders.atlassian.net/wiki/spaces/ORDER/pages/428180072/EVM+Listing+Symbols + symbolId2Hash[1] = 0x5a8133e52befca724670dbf2cade550c522c2410dd5b1189df675e99388f509d; + symbolId2Hash[2] = 0x7e83089239db756ee233fa8972addfea16ae653db0f692e4851aed546b21caeb; + symbolId2Hash[3] = 0x2f1991e99a4e22a9e95ff1b67aee336b4047dc47612e36674fa23eb8c6017f2e; + symbolId2Hash[4] = 0x3e5bb1a69a9094f1b2ccad4f39a7d70e2a29f08c2c0eac87b970ea650ac12ec2; + symbolId2Hash[5] = 0xb5ec44c9e46c5ae2fa0473eb8c466c97ec83dd5f4eddf66f31e83b512cff503c; + + symbolId2Hash[6] = 0x01bec50d553af75d1a2204c760570f374c438885070eb995500c7a08fc5a9ec2; + symbolId2Hash[7] = 0xe31e58f63b7cc1ad056bda9f1be47bf0ad0891a03d3a759f68c7814241a48907; + symbolId2Hash[8] = 0xc3d5ec779f548bc3d82ab3438416db751e7e1946827b31eeb1bd08e367278281; + symbolId2Hash[9] = 0xcaf4dffbbf83b8f5c74bb2946baeb3da1c6c7fc6290a899b18e95bb6f11c0503; + symbolId2Hash[10] = 0xa84558a42cda72af9bb348e8fc6cdfca9b3ddd885f1b8877abbc33beafc8bfec; + + symbolId2Hash[11] = 0x76bba29822652c557a30fe45ff09e7e244e3819699df0c0995622c12db16e72d; + symbolId2Hash[12] = 0xd44817bf72a4d9b5e277bfec92619466999b1adbd9f3c52621d1651ac354b09c; + symbolId2Hash[13] = 0x2aa4f612cf7a91de02395cadb419d3bf578130509b35b69c05738860e5b74637; + symbolId2Hash[14] = 0x5d0471b083610a6f3b572fc8b0f759c5628e74159816681fb7d927b9263de60b; + symbolId2Hash[15] = 0xa2adc016e890b4fbbf161c7eaeb615b893e4fbeceae918fa7bf16cc40d46610b; + } + + // empty function, for generate ABI for struct `PerpTypesZip.FuturesTradeUploadDataZip` + function placeholder(PerpTypesZip.FuturesTradeUploadDataZip calldata zip) external {} + + // internal function + function calcNotional(int128 tradeQty, uint128 executedPrice) internal pure returns (int128) { + // notional = tradeQty * executedPrice / 1e10, where tradeQty is int128, executedPrice is uint128 + // no worry about overflow, we expand the notional to int256 + return int128(int256(tradeQty) * int256(uint256((executedPrice))) / 1e10); + } +} diff --git a/test/FeeManager.t.sol b/test/FeeManager.t.sol new file mode 100644 index 0000000..691545d --- /dev/null +++ b/test/FeeManager.t.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.19; + +import "forge-std/Test.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; +import "../src/FeeManager.sol"; +import "../src/Ledger.sol"; + +contract FeeManagerTest is Test { + ProxyAdmin admin; + IFeeManager feeManager; + ILedger ledger; + TransparentUpgradeableProxy feeManagerProxy; + TransparentUpgradeableProxy ledgerManagerProxy; + bytes32 constant accountId1 = 0x6b97733ca568eddf2559232fa831f8de390a76d4f29a2962c3a9d0020383f7e3; + bytes32 constant accountId2 = 0x6b97733ca568eddf2559232fa831f8de390a76d4f29a2962c3a9d0020383f7e4; + bytes32 constant accountId3 = 0x6b97733ca568eddf2559232fa831f8de390a76d4f29a2962c3a9d0020383f7e5; + + function setUp() public { + admin = new ProxyAdmin(); + + IFeeManager feeManagerImpl = new FeeManager(); + ILedger ledgerImpl = new Ledger(); + + feeManagerProxy = new TransparentUpgradeableProxy(address(feeManagerImpl), address(admin), ""); + ledgerManagerProxy = new TransparentUpgradeableProxy(address(ledgerImpl), address(admin), ""); + + feeManager = IFeeManager(address(feeManagerProxy)); + ledger = ILedger(address(ledgerManagerProxy)); + + feeManager.initialize(); + ledger.initialize(); + + feeManager.setLedgerAddress(address(ledger)); + ledger.setFeeManager(address(feeManager)); + } + + function test_set_get() public { + feeManager.changeFeeCollector(IFeeManager.FeeCollectorType.WithdrawFeeCollector, accountId2); + assertEq(feeManager.getFeeCollector(IFeeManager.FeeCollectorType.WithdrawFeeCollector), accountId2); + feeManager.changeFeeCollector(IFeeManager.FeeCollectorType.FuturesFeeCollector, accountId3); + assertEq(feeManager.getFeeCollector(IFeeManager.FeeCollectorType.FuturesFeeCollector), accountId3); + } +} diff --git a/test/Ledger.t.sol b/test/Ledger.t.sol new file mode 100644 index 0000000..e49d285 --- /dev/null +++ b/test/Ledger.t.sol @@ -0,0 +1,231 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.19; + +import "forge-std/Test.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; +import "../src/OperatorManager.sol"; +import "../src/VaultManager.sol"; +import "../src/MarketManager.sol"; +import "../src/FeeManager.sol"; +import "./mock/LedgerCrossChainManagerMock.sol"; +import "./cheater/LedgerCheater.sol"; +import "../src/LedgerImplA.sol"; + +contract LedgerTest is Test { + ProxyAdmin admin; + address constant operatorAddress = address(0x1234567890); + LedgerCrossChainManagerMock ledgerCrossChainManager; + IOperatorManager operatorManager; + IVaultManager vaultManager; + LedgerCheater ledger; + IFeeManager feeManager; + IMarketManager marketManager; + TransparentUpgradeableProxy operatorProxy; + TransparentUpgradeableProxy vaultProxy; + TransparentUpgradeableProxy ledgerProxy; + TransparentUpgradeableProxy feeProxy; + TransparentUpgradeableProxy marketProxy; + + uint128 constant AMOUNT = 1000000; + address constant SENDER = 0xc7ef8C0853CCB92232Aa158b2AF3e364f1BaE9a1; + bytes32 constant ACCOUNT_ID = 0x6b97733ca568eddf2559232fa831f8de390a76d4f29a2962c3a9d0020383f7e3; + bytes32 constant BROKER_HASH = 0x083098c593f395bea1de45dda552d9f14e8fcb0be3faaa7a1903c5477d7ba7fd; + bytes32 constant TOKEN_HASH = 0xd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa; + uint256 constant CHAIN_ID = 986532; + uint64 constant WITHDRAW_NONCE = 233; + AccountTypes.AccountDeposit depositData = AccountTypes.AccountDeposit({ + accountId: ACCOUNT_ID, + brokerHash: BROKER_HASH, + userAddress: SENDER, + tokenHash: TOKEN_HASH, + tokenAmount: AMOUNT, + srcChainId: CHAIN_ID, + srcChainDepositNonce: 1 + }); + + // address(this) is 0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496 + EventTypes.WithdrawData withdrawData = EventTypes.WithdrawData( + AMOUNT, + 0, + CHAIN_ID, + ACCOUNT_ID, + 0x545c50021214976d1ef2ca5be753718b1b951050dc619c9ebb0a500465df0ac5, + 0x79f323773c4b34008e50e8b067a78669b341a3d5ebab1658847c9e03ff545cf3, + 0x1b, + SENDER, + WITHDRAW_NONCE, + SENDER, + 1688110729953, + "woofi_dex", + "USDC" + ); + + // address(this) is 0xD6BbDE9174b1CdAa358d2Cf4D57D1a9F7178FBfF + EventTypes.WithdrawData withdrawData2 = EventTypes.WithdrawData( + AMOUNT, + 0, + CHAIN_ID, + ACCOUNT_ID, + 0xd07bc78e77ab1dac61bcfce876189e6d0458920658f3cf20fdde16b8d55a6d03, + 0x24fe74240344f3c40b9674f68a11c117f7be62bc307a0d449d3da6484e9ae18e, + 0x1b, + SENDER, + WITHDRAW_NONCE, + SENDER, + 1688558006579, + "woofi_dex", + "USDC" + ); + + // // address(this) is 0x5991A2dF15A8F6A256D3Ec51E99254Cd3fb576A9 + // EventTypes.WithdrawData withdrawData2 = EventTypes.WithdrawData( + // AMOUNT, + // 0, + // CHAIN_ID, + // ACCOUNT_ID, + // 0xb107b0cb221d45555aa61fe9ea8ee372e4e310d6381f08cb99f06883836641ac, + // 0x0927097d7625e8b73f2d87c3a60a06204667305f0369c1aa79f4f71e1dc99bbf, + // 0x1b, + // SENDER, + // WITHDRAW_NONCE, + // SENDER, + // 1688111795719, + // "woofi_dex", + // "USDC" + // ); + + AccountTypes.AccountWithdraw accountWithdraw = AccountTypes.AccountWithdraw({ + accountId: ACCOUNT_ID, + sender: SENDER, + receiver: SENDER, + brokerHash: BROKER_HASH, + tokenHash: TOKEN_HASH, + tokenAmount: AMOUNT, + fee: 0, + chainId: CHAIN_ID, + withdrawNonce: WITHDRAW_NONCE + }); + + function setUp() public { + admin = new ProxyAdmin(); + + ledgerCrossChainManager = new LedgerCrossChainManagerMock(); + + IOperatorManager operatorManagerImpl = new OperatorManager(); + IVaultManager vaultManagerImpl = new VaultManager(); + ILedger ledgerImpl = new LedgerCheater(); + IFeeManager feeImpl = new FeeManager(); + IMarketManager marketImpl = new MarketManager(); + + bytes memory initData = abi.encodeWithSignature("initialize()"); + operatorProxy = new TransparentUpgradeableProxy(address(operatorManagerImpl), address(admin), initData); + vaultProxy = new TransparentUpgradeableProxy(address(vaultManagerImpl), address(admin), initData); + ledgerProxy = new TransparentUpgradeableProxy(address(ledgerImpl), address(admin), initData); + feeProxy = new TransparentUpgradeableProxy(address(feeImpl), address(admin), initData); + marketProxy = new TransparentUpgradeableProxy(address(marketImpl), address(admin), initData); + + operatorManager = IOperatorManager(address(operatorProxy)); + vaultManager = IVaultManager(address(vaultProxy)); + ledger = LedgerCheater(address(ledgerProxy)); + feeManager = IFeeManager(address(feeProxy)); + marketManager = IMarketManager(address(marketProxy)); + + // do not change the order + LedgerImplA ledgerImplA = new LedgerImplA(); + + ledger.setOperatorManagerAddress(address(operatorManager)); + ledger.setCrossChainManager(address(ledgerCrossChainManager)); + ledger.setVaultManager(address(vaultManager)); + ledger.setFeeManager(address(feeManager)); + ledger.setMarketManager(address(marketManager)); + ledger.setLedgerImplA(address(ledgerImplA)); + + operatorManager.setOperator(operatorAddress); + operatorManager.setLedger(address(ledger)); + + vaultManager.setLedgerAddress(address(ledger)); + if (!vaultManager.getAllowedToken(TOKEN_HASH)) { + vaultManager.setAllowedToken(TOKEN_HASH, true); + } + if (!vaultManager.getAllowedBroker(BROKER_HASH)) { + vaultManager.setAllowedBroker(BROKER_HASH, true); + } + vaultManager.setAllowedChainToken(TOKEN_HASH, CHAIN_ID, true); + + feeManager.setLedgerAddress(address(ledger)); + + marketManager.setOperatorManagerAddress(address(operatorManager)); + marketManager.setLedgerAddress(address(ledger)); + + ledgerCrossChainManager.setLedger(address(ledger)); + ledgerCrossChainManager.setOperatorManager(address(operatorManager)); + } + + function test_verify_EIP712() public { + vm.chainId(CHAIN_ID); + bool succ = Signature.verifyWithdraw(withdrawData.sender, withdrawData); + require(succ, "verify failed"); + } + + function testRevert_verify_EIP712() public { + withdrawData.chainId = 0xdead; + bool succ = Signature.verifyWithdraw(withdrawData.sender, withdrawData); + vm.expectRevert("verify failed"); + require(succ, "verify failed"); + } + + function test_deposit() public { + vm.prank(address(ledgerCrossChainManager)); + ledger.accountDeposit(depositData); + assertEq(ledger.getUserLedgerBalance(ACCOUNT_ID, TOKEN_HASH), AMOUNT); + assertEq(vaultManager.getBalance(TOKEN_HASH, CHAIN_ID), AMOUNT); + } + + function test_withdraw_approve() public { + vm.prank(address(ledgerCrossChainManager)); + ledger.accountDeposit(depositData); + vm.prank(address(operatorManager)); + vm.chainId(CHAIN_ID); + ledger.executeWithdrawAction(withdrawData2, 1); + assertEq(ledger.getUserLedgerBalance(ACCOUNT_ID, TOKEN_HASH), 0); + assertEq(ledger.getFrozenTotalBalance(ACCOUNT_ID, TOKEN_HASH), AMOUNT); + assertEq(ledger.getFrozenWithdrawNonce(ACCOUNT_ID, WITHDRAW_NONCE, TOKEN_HASH), AMOUNT); + } + + function testRevert_depositNotAllowedBroker() public { + vaultManager.setAllowedBroker(BROKER_HASH, false); + vm.prank(address(ledgerCrossChainManager)); + vm.expectRevert(IError.BrokerNotAllowed.selector); + ledger.accountDeposit(depositData); + } + + function testRevert_depositNotallowedChainToken() public { + vaultManager.setAllowedChainToken(TOKEN_HASH, CHAIN_ID, false); + vm.prank(address(ledgerCrossChainManager)); + vm.expectRevert(abi.encodeWithSelector(IError.TokenNotAllowed.selector, TOKEN_HASH, CHAIN_ID)); + ledger.accountDeposit(depositData); + } + + function testRevert_depositInvalidAccountId() public { + vm.prank(address(ledgerCrossChainManager)); + vm.expectRevert(IError.AccountIdInvalid.selector); + depositData.accountId = 0x44a4d91d025846561e99ca284b96d282bc1f183c12c36471c58dee3747487d99; + ledger.accountDeposit(depositData); + } + + function test_withdraw_finish() public { + vm.prank(address(ledgerCrossChainManager)); + ledger.accountDeposit(depositData); + vm.prank(address(operatorManager)); + vm.chainId(CHAIN_ID); + ledger.executeWithdrawAction(withdrawData2, 1); + assertEq(ledger.getUserLedgerBalance(ACCOUNT_ID, TOKEN_HASH), 0); + assertEq(ledger.getFrozenTotalBalance(ACCOUNT_ID, TOKEN_HASH), AMOUNT); + assertEq(ledger.getFrozenWithdrawNonce(ACCOUNT_ID, WITHDRAW_NONCE, TOKEN_HASH), AMOUNT); + ledgerCrossChainManager.withdrawFinishMock(accountWithdraw); + assertEq(ledger.getUserLedgerBalance(ACCOUNT_ID, TOKEN_HASH), 0); + assertEq(ledger.getFrozenTotalBalance(ACCOUNT_ID, TOKEN_HASH), 0); + assertEq(ledger.getFrozenWithdrawNonce(ACCOUNT_ID, WITHDRAW_NONCE, TOKEN_HASH), 0); + } +} diff --git a/test/OperatorManager.t.sol b/test/OperatorManager.t.sol index d07184c..de0ffae 100644 --- a/test/OperatorManager.t.sol +++ b/test/OperatorManager.t.sol @@ -2,22 +2,55 @@ pragma solidity ^0.8.19; import "forge-std/Test.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; import "../src/OperatorManager.sol"; -import "../src/Settlement.sol"; -import "../src/CrossChainManager.sol"; +import "../src/Ledger.sol"; +import "../src/VaultManager.sol"; +import "./mock/LedgerCrossChainManagerMock.sol"; contract OperatorManagerTest is Test { + ProxyAdmin admin; address constant operatorAddress = address(0x1234567890); - ICrossChainManager crossChainManager; + ILedgerCrossChainManager ledgerCrossChainManager; IOperatorManager operatorManager; - ISettlement settlement; + IVaultManager vaultManager; + ILedger ledger; + TransparentUpgradeableProxy operatorProxy; + TransparentUpgradeableProxy vaultProxy; + TransparentUpgradeableProxy ledgerProxy; function setUp() public { - crossChainManager = new CrossChainManager(); - operatorManager = new OperatorManager(); - settlement = new Settlement(address(operatorManager), address(crossChainManager)); + admin = new ProxyAdmin(); + + ledgerCrossChainManager = new LedgerCrossChainManagerMock(); + IOperatorManager operatorManagerImpl = new OperatorManager(); + IVaultManager vaultManagerImpl = new VaultManager(); + ILedger ledgerImpl = new Ledger(); + + operatorProxy = new TransparentUpgradeableProxy(address(operatorManagerImpl), address(admin), ""); + vaultProxy = new TransparentUpgradeableProxy(address(vaultManagerImpl), address(admin), ""); + ledgerProxy = new TransparentUpgradeableProxy(address(ledgerImpl), address(admin), ""); + + operatorManager = IOperatorManager(address(operatorProxy)); + vaultManager = IVaultManager(address(vaultProxy)); + ledger = ILedger(address(ledgerProxy)); + + operatorManager.initialize(); + vaultManager.initialize(); + ledger.initialize(); + + ledger.setOperatorManagerAddress(address(operatorManager)); + ledger.setCrossChainManager(address(ledgerCrossChainManager)); + ledger.setVaultManager(address(vaultManager)); + operatorManager.setOperator(operatorAddress); - operatorManager.setSettlement(address(settlement)); + operatorManager.setLedger(address(ledger)); + + ledgerCrossChainManager.setLedger(address(ledger)); + ledgerCrossChainManager.setOperatorManager(address(operatorManager)); + + vaultManager.setLedgerAddress(address(ledger)); } function test_ping() public { @@ -25,19 +58,14 @@ contract OperatorManagerTest is Test { operatorManager.operatorPing(); } - function testFail_pingNotOperator() public { + function testRevert_pingNotOperator() public { vm.prank(address(0x1)); + vm.expectRevert(IError.OnlyOperatorCanCall.selector); operatorManager.operatorPing(); } - function test_register() public { - vm.prank(operatorAddress); - AccountTypes.AccountRegister memory accountRegister = AccountTypes.AccountRegister( - bytes32(0x847928ac5e1d1e0867035b1fcff57798ce1652ef12664ee4c387463acc502e93), - address(0xc764E58c95B5b702abBe775FE09dc8F653Ea9e1a), - bytes32(0xfb08c0b22085b07c3787ca55e02cc585a966b0799bfef3d32fc335d7107cedef) - ); - operatorManager.accountRegisterAction(accountRegister); - assertEq(settlement.getUserLedgerBrokerId(accountRegister.accountId), accountRegister.brokerId); + function test_engineNotDown() public { + bool isDown = operatorManager.checkEngineDown(); + assertEq(isDown, false); } } diff --git a/test/OperatorManagerZip.t.sol b/test/OperatorManagerZip.t.sol new file mode 100644 index 0000000..1c7a1dd --- /dev/null +++ b/test/OperatorManagerZip.t.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.19; + +import "forge-std/Test.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; +import "../src/OperatorManager.sol"; +import "../src/zip/OperatorManagerZip.sol"; +import "forge-std/console.sol"; + +// https://www.rareskills.io/post/solidity-test-internal-function +contract OperatorManagerZipHarness is OperatorManagerZip { + function calcNotionalExternal(int128 tradeQty, uint128 executedPrice) external pure returns (int128) { + return super.calcNotional(tradeQty, executedPrice); + } +} + +contract OperatorManagerZipTest is Test { + ProxyAdmin admin; + address constant operatorAddress = address(0xDdDd1555A17d3Dad86748B883d2C1ce633A7cd88); + IOperatorManager operatorManager; + IOperatorManagerZip operatorManagerZip; + TransparentUpgradeableProxy operatorManagerProxy; + TransparentUpgradeableProxy operatorManagerZipProxy; + OperatorManagerZipHarness zipHarness; + + function setUp() public { + admin = new ProxyAdmin(); + + IOperatorManager operatorManagerImpl = new OperatorManager(); + IOperatorManagerZip operatorManagerZipImpl = new OperatorManagerZip(); + + bytes memory initData = abi.encodeWithSignature("initialize()"); + + operatorManagerProxy = new TransparentUpgradeableProxy(address(operatorManagerImpl), address(admin), initData); + operatorManagerZipProxy = + new TransparentUpgradeableProxy(address(operatorManagerZipImpl), address(admin), initData); + + operatorManager = IOperatorManager(address(operatorManagerProxy)); + operatorManagerZip = IOperatorManagerZip(address(operatorManagerZipProxy)); + + operatorManagerZip.setOperator(operatorAddress); + operatorManagerZip.setOpeartorManager(address(operatorManager)); + + zipHarness = new OperatorManagerZipHarness(); + } + + function test_notionalCalc1() public { + int128 tradeQty = 26400000000; + uint128 executedPrice = 234950000; + assertEq(zipHarness.calcNotionalExternal(tradeQty, executedPrice), 620268000); + } + + function test_notionalCalc2() public { + int128 tradeQty = -26400000000; + uint128 executedPrice = 234950000; + assertEq(zipHarness.calcNotionalExternal(tradeQty, executedPrice), -620268000); + } +} diff --git a/test/UpgradeContract.t.sol b/test/UpgradeContract.t.sol new file mode 100644 index 0000000..bac1904 --- /dev/null +++ b/test/UpgradeContract.t.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.19; + +import "forge-std/Test.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; +import "../src/VaultManager.sol"; +import "../src/Ledger.sol"; +import "./mock/VaultManagerBuggy.sol"; + +contract VaultManagerTest is Test { + ProxyAdmin admin; + IVaultManager vaultManager; + ILedger ledger; + TransparentUpgradeableProxy vaultManagerProxy; + TransparentUpgradeableProxy ledgerManagerProxy; + uint256 constant CHAIN_ID = 0xabcd; + bytes32 constant TOKEN_HASH = 0x61fc29e9a6b4b52b423e75ca44734454f94ea60ddff3dc47af01a2a646fe9572; + + function setUp() public { + admin = new ProxyAdmin(); + + IVaultManager vaultManagerImpl = new VaultManagerBuggy(); + ILedger ledgerImpl = new Ledger(); + + vaultManagerProxy = new TransparentUpgradeableProxy(address(vaultManagerImpl), address(admin), ""); + ledgerManagerProxy = new TransparentUpgradeableProxy(address(ledgerImpl), address(admin), ""); + + vaultManager = IVaultManager(address(vaultManagerProxy)); + ledger = ILedger(address(ledgerManagerProxy)); + + vaultManager.initialize(); + ledger.initialize(); + + vaultManager.setLedgerAddress(address(ledger)); + ledger.setVaultManager(address(vaultManager)); + } + + function testFail_sub_add_get() public { + // should fail due to VaultManagerBuggy + vm.startPrank(address(ledger)); + vaultManager.addBalance(TOKEN_HASH, CHAIN_ID, 100); + assertEq(vaultManager.getBalance(TOKEN_HASH, CHAIN_ID), 100); + vaultManager.addBalance(TOKEN_HASH, CHAIN_ID, 200); + assertEq(vaultManager.getBalance(TOKEN_HASH, CHAIN_ID), 300); + vaultManager.subBalance(TOKEN_HASH, CHAIN_ID, 150); + assertEq(vaultManager.getBalance(TOKEN_HASH, CHAIN_ID), 150); + vm.stopPrank(); + } + + function test_sub_add_get() public { + // upgrade to the correct implementation + admin.upgrade(ITransparentUpgradeableProxy(address(vaultManagerProxy)), address(new VaultManager())); + + vm.startPrank(address(ledger)); + vaultManager.addBalance(TOKEN_HASH, CHAIN_ID, 100); + assertEq(vaultManager.getBalance(TOKEN_HASH, CHAIN_ID), 100); + vaultManager.addBalance(TOKEN_HASH, CHAIN_ID, 200); + assertEq(vaultManager.getBalance(TOKEN_HASH, CHAIN_ID), 300); + vaultManager.subBalance(TOKEN_HASH, CHAIN_ID, 150); + assertEq(vaultManager.getBalance(TOKEN_HASH, CHAIN_ID), 150); + vm.stopPrank(); + } +} diff --git a/test/Vault.t.sol b/test/Vault.t.sol new file mode 100644 index 0000000..bb8da4d --- /dev/null +++ b/test/Vault.t.sol @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.19; + +import "forge-std/Test.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; +import "../src/vaultSide/Vault.sol"; +import "../src/vaultSide/tUSDC.sol"; +import "./mock/VaultCrossChainManagerMock.sol"; + +contract VaultTest is Test { + ProxyAdmin admin; + IVaultCrossChainManager vaultCrossChainManager; + TestUSDC tUSDC; + IVault vault; + TransparentUpgradeableProxy vaultProxy; + uint128 constant AMOUNT = 1000000; + address constant SENDER = 0x4FDDB51ADe1fa66952de254bE7E1a84EEB153331; + bytes32 constant ACCOUNT_ID = 0x89bf2019fe60f13ec6c3f8de8c10156c2691ba5e743260dbcd81c2c66e87cba0; + bytes32 constant BROKER_HASH = 0x083098c593f395bea1de45dda552d9f14e8fcb0be3faaa7a1903c5477d7ba7fd; // woofi_dex + bytes32 constant TOKEN_HASH = 0xd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa; // USDC + VaultTypes.VaultDepositFE depositData = VaultTypes.VaultDepositFE({ + accountId: ACCOUNT_ID, + brokerHash: BROKER_HASH, + tokenHash: TOKEN_HASH, + tokenAmount: AMOUNT + }); + VaultTypes.VaultWithdraw withdrawData = VaultTypes.VaultWithdraw({ + accountId: ACCOUNT_ID, + sender: SENDER, + receiver: SENDER, + brokerHash: BROKER_HASH, + tokenHash: TOKEN_HASH, + tokenAmount: AMOUNT, + fee: 0, + withdrawNonce: 0 + }); + + function setUp() public { + admin = new ProxyAdmin(); + + tUSDC = new TestUSDC(); + IVault vaultImpl = new Vault(); + vaultProxy = new TransparentUpgradeableProxy(address(vaultImpl), address(admin), ""); + vault = IVault(address(vaultProxy)); + vault.initialize(); + + vault.changeTokenAddressAndAllow(TOKEN_HASH, address(tUSDC)); + vault.setAllowedBroker(BROKER_HASH, true); + vaultCrossChainManager = new VaultCrossChainManagerMock(); + vault.setCrossChainManager(address(vaultCrossChainManager)); + } + + function test_deposit() public { + vm.startPrank(SENDER); + tUSDC.mint(SENDER, AMOUNT); + tUSDC.approve(address(vault), AMOUNT); + assertEq(tUSDC.balanceOf(address(SENDER)), AMOUNT); + + vault.deposit(depositData); + vm.stopPrank(); + assertEq(tUSDC.balanceOf(address(SENDER)), 0); + assertEq(tUSDC.balanceOf(address(vault)), AMOUNT); + } + + function testRevert_depositInsufficientAmount() public { + vm.startPrank(SENDER); + tUSDC.mint(SENDER, AMOUNT - 1); + tUSDC.approve(address(vault), AMOUNT); + assertEq(tUSDC.balanceOf(address(SENDER)), AMOUNT - 1); + + vm.expectRevert("ERC20: transfer amount exceeds balance"); + vault.deposit(depositData); + } + + function testRevert_depositInsufficientApproval() public { + vm.startPrank(SENDER); + tUSDC.mint(SENDER, AMOUNT); + tUSDC.approve(address(vault), AMOUNT - 1); + assertEq(tUSDC.balanceOf(address(SENDER)), AMOUNT); + + vm.expectRevert("ERC20: insufficient allowance"); + vault.deposit(depositData); + } + + function testRevert_depositNotAllowedToken() public { + vm.startPrank(SENDER); + depositData.tokenHash = 0x96706879d29c248edfb2a2563a8a9d571c49634c0f82013e6f5a7cde739d35d4; // "TOKEN" + + vm.expectRevert(IVault.TokenNotAllowed.selector); + vault.deposit(depositData); + vm.stopPrank(); + } + + function testRevert_depositNotAllowedBroker() public { + vm.startPrank(SENDER); + depositData.brokerHash = 0x2804e22f743595918807e939e50f80985ef77d3aa68cd82cff712cc69eee98ec; // "brokerId" + vm.expectRevert(IVault.BrokerNotAllowed.selector); + vault.deposit(depositData); + vm.stopPrank(); + } + + function testRevert_depositIncorrectAccountId() public { + vm.startPrank(SENDER); + tUSDC.mint(SENDER, AMOUNT); + tUSDC.approve(address(vault), AMOUNT); + assertEq(tUSDC.balanceOf(address(SENDER)), AMOUNT); + depositData.accountId = 0x44a4d91d025846561e99ca284b96d282bc1f183c12c36471c58dee3747487d99; // keccak(SENDER, keccak("brokerId")) + vm.expectRevert(IVault.AccountIdInvalid.selector); + vault.deposit(depositData); + vm.stopPrank(); + } + + function test_withdraw() public { + vm.startPrank(SENDER); + tUSDC.mint(SENDER, AMOUNT); + tUSDC.approve(address(vault), AMOUNT); + vault.deposit(depositData); + vm.stopPrank(); + + vm.prank(address(vaultCrossChainManager)); + vault.withdraw(withdrawData); + assertEq(tUSDC.balanceOf(address(SENDER)), AMOUNT); + assertEq(tUSDC.balanceOf(address(vault)), 0); + } + + function testRevert_withdrawInsufficientBalance() public { + vm.startPrank(SENDER); + tUSDC.mint(SENDER, AMOUNT); + tUSDC.approve(address(vault), AMOUNT); + depositData.tokenAmount = AMOUNT - 1; + vault.deposit(depositData); + vm.stopPrank(); + + vm.prank(address(vaultCrossChainManager)); + vm.expectRevert(); + vault.withdraw(withdrawData); + } + + function test_getAllWhitelistSet() public { + vault.changeTokenAddressAndAllow(TOKEN_HASH, address(tUSDC)); + if (!vault.getAllowedBroker(BROKER_HASH)) { + vault.setAllowedBroker(BROKER_HASH, true); + } + assertEq(vault.getAllAllowedBroker().length, 1); + assertEq(vault.getAllAllowedToken().length, 1); + } + + function test_whitelist() public { + vault.changeTokenAddressAndAllow(TOKEN_HASH, address(tUSDC)); + if (!vault.getAllowedBroker(BROKER_HASH)) { + vault.setAllowedBroker(BROKER_HASH, true); + } + assertEq(vault.getAllowedToken(TOKEN_HASH), address(tUSDC)); + assertEq(vault.getAllowedBroker(BROKER_HASH), true); + + vault.setAllowedToken(TOKEN_HASH, false); + if (vault.getAllowedBroker(BROKER_HASH)) { + vault.setAllowedBroker(BROKER_HASH, false); + } + assertEq(vault.getAllowedToken(TOKEN_HASH), address(0)); + assertEq(vault.getAllowedBroker(BROKER_HASH), false); + + vault.setAllowedToken(TOKEN_HASH, true); + if (!vault.getAllowedBroker(BROKER_HASH)) { + vault.setAllowedBroker(BROKER_HASH, true); + } + assertEq(vault.getAllowedToken(TOKEN_HASH), address(tUSDC)); + assertEq(vault.getAllowedBroker(BROKER_HASH), true); + } +} diff --git a/test/VaultManager.t.sol b/test/VaultManager.t.sol new file mode 100644 index 0000000..cd34746 --- /dev/null +++ b/test/VaultManager.t.sol @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.19; + +import "forge-std/Test.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; +import "../src/VaultManager.sol"; +import "../src/Ledger.sol"; + +contract VaultManagerTest is Test { + ProxyAdmin admin; + IVaultManager vaultManager; + ILedger ledger; + TransparentUpgradeableProxy vaultManagerProxy; + TransparentUpgradeableProxy ledgerManagerProxy; + uint256 constant CHAIN_ID = 0xabcd; + bytes32 constant TOKEN_HASH = 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; + bytes32 constant BROKER_HASH = 0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb; + bytes32 constant SYMBOL_HASH = 0xcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc; + + function setUp() public { + admin = new ProxyAdmin(); + + IVaultManager vaultManagerImpl = new VaultManager(); + ILedger ledgerImpl = new Ledger(); + + vaultManagerProxy = new TransparentUpgradeableProxy(address(vaultManagerImpl), address(admin), ""); + ledgerManagerProxy = new TransparentUpgradeableProxy(address(ledgerImpl), address(admin), ""); + + vaultManager = IVaultManager(address(vaultManagerProxy)); + ledger = ILedger(address(ledgerManagerProxy)); + + vaultManager.initialize(); + ledger.initialize(); + + vaultManager.setLedgerAddress(address(ledger)); + ledger.setVaultManager(address(vaultManager)); + } + + function test_sub_add_get() public { + vm.startPrank(address(ledger)); + vaultManager.addBalance(TOKEN_HASH, CHAIN_ID, 100); + assertEq(vaultManager.getBalance(TOKEN_HASH, CHAIN_ID), 100); + vaultManager.addBalance(TOKEN_HASH, CHAIN_ID, 200); + assertEq(vaultManager.getBalance(TOKEN_HASH, CHAIN_ID), 300); + vaultManager.subBalance(TOKEN_HASH, CHAIN_ID, 150); + assertEq(vaultManager.getBalance(TOKEN_HASH, CHAIN_ID), 150); + vm.stopPrank(); + } + + function testFail_sub_overflow() public { + vm.startPrank(address(ledger)); + vaultManager.addBalance(TOKEN_HASH, CHAIN_ID, 100); + assertEq(vaultManager.getBalance(TOKEN_HASH, CHAIN_ID), 100); + vaultManager.subBalance(TOKEN_HASH, CHAIN_ID, 150); + vm.stopPrank(); + } + + function test_frozen_finish_frozen() public { + vm.startPrank(address(ledger)); + vaultManager.addBalance(TOKEN_HASH, CHAIN_ID, 300); + assertEq(vaultManager.getBalance(TOKEN_HASH, CHAIN_ID), 300); + vaultManager.frozenBalance(TOKEN_HASH, CHAIN_ID, 200); + assertEq(vaultManager.getBalance(TOKEN_HASH, CHAIN_ID), 100); + assertEq(vaultManager.getFrozenBalance(TOKEN_HASH, CHAIN_ID), 200); + vaultManager.finishFrozenBalance(TOKEN_HASH, CHAIN_ID, 200); + assertEq(vaultManager.getBalance(TOKEN_HASH, CHAIN_ID), 100); + assertEq(vaultManager.getFrozenBalance(TOKEN_HASH, CHAIN_ID), 0); + vm.stopPrank(); + } + + function test_frozen_finish_frozen_zero() public { + vm.startPrank(address(ledger)); + vaultManager.addBalance(TOKEN_HASH, CHAIN_ID, 300); + assertEq(vaultManager.getBalance(TOKEN_HASH, CHAIN_ID), 300); + vaultManager.frozenBalance(TOKEN_HASH, CHAIN_ID, 200); + assertEq(vaultManager.getBalance(TOKEN_HASH, CHAIN_ID), 100); + assertEq(vaultManager.getFrozenBalance(TOKEN_HASH, CHAIN_ID), 200); + vaultManager.finishFrozenBalance(TOKEN_HASH, CHAIN_ID, 200); + assertEq(vaultManager.getBalance(TOKEN_HASH, CHAIN_ID), 100); + assertEq(vaultManager.getFrozenBalance(TOKEN_HASH, CHAIN_ID), 0); + vaultManager.frozenBalance(TOKEN_HASH, CHAIN_ID, 100); + assertEq(vaultManager.getBalance(TOKEN_HASH, CHAIN_ID), 0); + assertEq(vaultManager.getFrozenBalance(TOKEN_HASH, CHAIN_ID), 100); + vaultManager.finishFrozenBalance(TOKEN_HASH, CHAIN_ID, 100); + assertEq(vaultManager.getBalance(TOKEN_HASH, CHAIN_ID), 0); + assertEq(vaultManager.getFrozenBalance(TOKEN_HASH, CHAIN_ID), 0); + vm.stopPrank(); + } + + function testFail_frozen_overflow_1() public { + vm.startPrank(address(ledger)); + vaultManager.addBalance(TOKEN_HASH, CHAIN_ID, 100); + assertEq(vaultManager.getBalance(TOKEN_HASH, CHAIN_ID), 100); + vaultManager.frozenBalance(TOKEN_HASH, CHAIN_ID, 150); + vm.stopPrank(); + } + + function testFail_frozen_overflow_2() public { + vm.startPrank(address(ledger)); + vaultManager.addBalance(TOKEN_HASH, CHAIN_ID, 200); + assertEq(vaultManager.getBalance(TOKEN_HASH, CHAIN_ID), 200); + vaultManager.subBalance(TOKEN_HASH, CHAIN_ID, 100); + assertEq(vaultManager.getBalance(TOKEN_HASH, CHAIN_ID), 100); + vaultManager.frozenBalance(TOKEN_HASH, CHAIN_ID, 150); + vm.stopPrank(); + } + + function test_getAllWhitelistSet() public { + uint256 brokerLength = vaultManager.getAllAllowedBroker().length; + uint256 tokenLength = vaultManager.getAllAllowedToken().length; + uint256 symbolLength = vaultManager.getAllAllowedSymbol().length; + vaultManager.setAllowedChainToken(TOKEN_HASH, CHAIN_ID, true); + vaultManager.setAllowedToken(TOKEN_HASH, true); + vaultManager.setAllowedBroker(BROKER_HASH, true); + vaultManager.setAllowedSymbol(SYMBOL_HASH, true); + assertEq(vaultManager.getAllAllowedBroker().length, brokerLength + 1); + assertEq(vaultManager.getAllAllowedToken().length, tokenLength + 1); + assertEq(vaultManager.getAllAllowedSymbol().length, symbolLength + 1); + } + + function test_setThenUnsetWhitelist() public { + vaultManager.setAllowedChainToken(TOKEN_HASH, CHAIN_ID, true); + vaultManager.setAllowedToken(TOKEN_HASH, true); + vaultManager.setAllowedBroker(BROKER_HASH, true); + vaultManager.setAllowedSymbol(SYMBOL_HASH, true); + assertTrue(vaultManager.getAllowedBroker(BROKER_HASH)); + assertTrue(vaultManager.getAllowedToken(TOKEN_HASH)); + assertTrue(vaultManager.getAllowedSymbol(SYMBOL_HASH)); + vaultManager.setAllowedChainToken(TOKEN_HASH, CHAIN_ID, false); + vaultManager.setAllowedToken(TOKEN_HASH, false); + vaultManager.setAllowedBroker(BROKER_HASH, false); + vaultManager.setAllowedSymbol(SYMBOL_HASH, false); + assertFalse(vaultManager.getAllowedBroker(BROKER_HASH)); + assertFalse(vaultManager.getAllowedToken(TOKEN_HASH)); + assertFalse(vaultManager.getAllowedSymbol(SYMBOL_HASH)); + } + + function test_chainTokenAllowance() public { + vaultManager.setAllowedChainToken(TOKEN_HASH, CHAIN_ID, true); + vaultManager.setAllowedToken(TOKEN_HASH, true); + assertTrue(vaultManager.getAllowedChainToken(TOKEN_HASH, CHAIN_ID)); + assertTrue(vaultManager.getAllowedToken(TOKEN_HASH)); + vaultManager.setAllowedChainToken(TOKEN_HASH, CHAIN_ID, false); + assertFalse(vaultManager.getAllowedChainToken(TOKEN_HASH, CHAIN_ID)); + assertTrue(vaultManager.getAllowedToken(TOKEN_HASH)); + vaultManager.setAllowedChainToken(TOKEN_HASH, CHAIN_ID, true); + vaultManager.setAllowedToken(TOKEN_HASH, false); + assertFalse(vaultManager.getAllowedChainToken(TOKEN_HASH, CHAIN_ID)); + assertFalse(vaultManager.getAllowedToken(TOKEN_HASH)); + } +} diff --git a/test/cheater/LedgerCheater.sol b/test/cheater/LedgerCheater.sol new file mode 100644 index 0000000..7856076 --- /dev/null +++ b/test/cheater/LedgerCheater.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol"; +import "../../src/Ledger.sol"; + +contract LedgerCheater is Ledger { + using AccountTypeHelper for AccountTypes.Account; + + function cheatDeposit(bytes32 accountId, bytes32 tokenHash, uint128 tokenAmount, uint256 srcChainId) external { + AccountTypes.Account storage account = userLedger[accountId]; + + account.addBalance(tokenHash, tokenAmount); + vaultManager.addBalance(tokenHash, srcChainId, tokenAmount); + account.lastDepositEventId = _newGlobalDepositId(); + } + + function cheatSetUserPosition(bytes32 accountId, bytes32 symbolHash, AccountTypes.PerpPosition memory position) + external + { + AccountTypes.Account storage account = userLedger[accountId]; + account.perpPositions[symbolHash] = position; + } + + // get userLedger balance + function getUserLedgerBalance(bytes32 accountId, bytes32 tokenHash) public view returns (uint128) { + return userLedger[accountId].getBalance(tokenHash); + } + + // get userLedger lastEngineEventId + function getUserLedgerLastEngineEventId(bytes32 accountId) public view returns (uint64) { + return userLedger[accountId].getLastEngineEventId(); + } + + // get frozen total balance + function getFrozenTotalBalance(bytes32 accountId, bytes32 tokenHash) public view returns (uint128) { + return userLedger[accountId].getFrozenTotalBalance(tokenHash); + } + + // get perp position + function getPerpPosition(bytes32 accountId, bytes32 symbolHash) + public + view + returns (AccountTypes.PerpPosition memory perpPosition) + { + perpPosition = userLedger[accountId].perpPositions[symbolHash]; + } + + function _newGlobalEventId() internal returns (uint64) { + return ++globalEventId; + } + + function _newGlobalDepositId() internal returns (uint64) { + return ++globalDepositId; + } +} diff --git a/test/functionTest/Adl.t.sol b/test/functionTest/Adl.t.sol new file mode 100644 index 0000000..0477971 --- /dev/null +++ b/test/functionTest/Adl.t.sol @@ -0,0 +1,311 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.19; + +import "forge-std/Test.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; +import "../../src/OperatorManager.sol"; +import "../../src/VaultManager.sol"; +import "../../src/MarketManager.sol"; +import "../../src/FeeManager.sol"; +import "../mock/LedgerCrossChainManagerMock.sol"; +import "../cheater/LedgerCheater.sol"; +import "../../src/LedgerImplA.sol"; + +contract AdlTest is Test { + bytes32 constant BROKER_HASH = 0x083098c593f395bea1de45dda552d9f14e8fcb0be3faaa7a1903c5477d7ba7fd; + bytes32 constant TOKEN_HASH = 0xd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa; + bytes32 constant SYMBOL_HASH_BTC_USDC = 0x1111101010101010101010101010101010101010101010101010101010101010; + bytes32 constant SYMBOL_HASH_ETH_USDC = 0x2222101010101010101010101010101010101010101010101010101010101010; + uint256 constant CHAIN_ID = 986532; + bytes32 constant ALICE = 0xa11ce00000000000000000000000000000000000000000000000000000000000; + bytes32 constant BOB = 0xb0b0000000000000000000000000000000000000000000000000000000000000; + bytes32 constant CHARLIE = 0x0300000000000000000000000000000000000000000000000000000000000000; + bytes32 constant INSURANCE_FUND = 0x1234123412341234123412341234123412341234123412341234123412341234; + + ProxyAdmin admin; + address constant operatorAddress = address(0x1234567890); + LedgerCrossChainManagerMock ledgerCrossChainManager; + IOperatorManager operatorManager; + IVaultManager vaultManager; + LedgerCheater ledger; + IFeeManager feeManager; + IMarketManager marketManager; + TransparentUpgradeableProxy operatorProxy; + TransparentUpgradeableProxy vaultProxy; + TransparentUpgradeableProxy ledgerProxy; + TransparentUpgradeableProxy feeProxy; + TransparentUpgradeableProxy marketProxy; + + function setUp() public { + admin = new ProxyAdmin(); + + ledgerCrossChainManager = new LedgerCrossChainManagerMock(); + + IOperatorManager operatorManagerImpl = new OperatorManager(); + IVaultManager vaultManagerImpl = new VaultManager(); + ILedger ledgerImpl = new LedgerCheater(); + IFeeManager feeImpl = new FeeManager(); + IMarketManager marketImpl = new MarketManager(); + LedgerImplA ledgerImplA = new LedgerImplA(); + + bytes memory initData = abi.encodeWithSignature("initialize()"); + operatorProxy = new TransparentUpgradeableProxy(address(operatorManagerImpl), address(admin), initData); + vaultProxy = new TransparentUpgradeableProxy(address(vaultManagerImpl), address(admin), initData); + ledgerProxy = new TransparentUpgradeableProxy(address(ledgerImpl), address(admin), initData); + feeProxy = new TransparentUpgradeableProxy(address(feeImpl), address(admin), initData); + marketProxy = new TransparentUpgradeableProxy(address(marketImpl), address(admin), initData); + + operatorManager = IOperatorManager(address(operatorProxy)); + vaultManager = IVaultManager(address(vaultProxy)); + ledger = LedgerCheater(address(ledgerProxy)); + feeManager = IFeeManager(address(feeProxy)); + marketManager = IMarketManager(address(marketProxy)); + + ledger.setOperatorManagerAddress(address(operatorManager)); + ledger.setCrossChainManager(address(ledgerCrossChainManager)); + ledger.setVaultManager(address(vaultManager)); + ledger.setFeeManager(address(feeManager)); + ledger.setMarketManager(address(marketManager)); + ledger.setLedgerImplA(address(ledgerImplA)); + + operatorManager.setOperator(operatorAddress); + operatorManager.setLedger(address(ledger)); + + vaultManager.setLedgerAddress(address(ledger)); + if (!vaultManager.getAllowedToken(TOKEN_HASH)) { + vaultManager.setAllowedToken(TOKEN_HASH, true); + } + if (!vaultManager.getAllowedBroker(BROKER_HASH)) { + vaultManager.setAllowedBroker(BROKER_HASH, true); + } + if (!vaultManager.getAllowedSymbol(SYMBOL_HASH_BTC_USDC)) { + vaultManager.setAllowedSymbol(SYMBOL_HASH_BTC_USDC, true); + } + if (!vaultManager.getAllowedSymbol(SYMBOL_HASH_ETH_USDC)) { + vaultManager.setAllowedSymbol(SYMBOL_HASH_ETH_USDC, true); + } + vaultManager.setAllowedChainToken(TOKEN_HASH, CHAIN_ID, true); + + feeManager.setLedgerAddress(address(ledger)); + + marketManager.setOperatorManagerAddress(address(operatorManager)); + marketManager.setLedgerAddress(address(ledger)); + marketManager.setPerpMarketCfg( + SYMBOL_HASH_BTC_USDC, + MarketTypes.PerpMarketCfg({ + baseMaintenanceMargin: 1, + baseInitialMargin: 1, + liquidationFeeMax: 1, + markPrice: 1, + indexPriceOrderly: 1, + sumUnitaryFundings: 1, + lastMarkPriceUpdated: 1, + lastFundingUpdated: 1 + }) + ); + + ledgerCrossChainManager.setLedger(address(ledger)); + ledgerCrossChainManager.setOperatorManager(address(operatorManager)); + + ledger.cheatDeposit(ALICE, TOKEN_HASH, 1_000_000_000, CHAIN_ID); + ledger.cheatDeposit(BOB, TOKEN_HASH, 1_000_000_000, CHAIN_ID); + ledger.cheatDeposit(CHARLIE, TOKEN_HASH, 1_000_000_000, CHAIN_ID); + ledger.cheatDeposit(INSURANCE_FUND, TOKEN_HASH, 10_000_000_000, CHAIN_ID); + + ledger.cheatSetUserPosition( + INSURANCE_FUND, + SYMBOL_HASH_ETH_USDC, + AccountTypes.PerpPosition({ + positionQty: 2_000_000_000, + costPosition: 100_000_000, + lastSumUnitaryFundings: 10_000_000_000_000_000, + lastExecutedPrice: 20_000_000, + lastSettledPrice: 0, + averageEntryPrice: 2_000_000_000, + openingCost: 0, + lastAdlPrice: 20_000_000 + }) + ); + + ledger.cheatSetUserPosition( + INSURANCE_FUND, + SYMBOL_HASH_BTC_USDC, + AccountTypes.PerpPosition({ + positionQty: -2_000_000_000, + costPosition: 100_000_000, + lastSumUnitaryFundings: 10_000_000_000_000_000, + lastExecutedPrice: 30_000_000, + lastSettledPrice: 0, + averageEntryPrice: 30_000_000, + openingCost: 0, + lastAdlPrice: 30_000_000 + }) + ); + + ledger.cheatSetUserPosition( + BOB, + SYMBOL_HASH_ETH_USDC, + AccountTypes.PerpPosition({ + positionQty: -1_000_000_000, + costPosition: 100_000_000, + lastSumUnitaryFundings: 10_000_000_000_000_000, + lastExecutedPrice: 20_000_000, + lastSettledPrice: 0, + averageEntryPrice: 2_000_000_000, + openingCost: 0, + lastAdlPrice: 20_000_000 + }) + ); + + ledger.cheatSetUserPosition( + BOB, + SYMBOL_HASH_BTC_USDC, + AccountTypes.PerpPosition({ + positionQty: 1_000_000_000, + costPosition: 100_000_000, + lastSumUnitaryFundings: 10_000_000_000_000_000, + lastExecutedPrice: 30_000_000, + lastSettledPrice: 0, + averageEntryPrice: 3_000_000_000, + openingCost: 0, + lastAdlPrice: 30_000_000 + }) + ); + + ledger.cheatSetUserPosition( + CHARLIE, + SYMBOL_HASH_ETH_USDC, + AccountTypes.PerpPosition({ + positionQty: -500_000_000, + costPosition: 100_000_000, + lastSumUnitaryFundings: 10_000_000_000_000_000, + lastExecutedPrice: 20_000_000, + lastSettledPrice: 0, + averageEntryPrice: 2_000_000_000, + openingCost: 0, + lastAdlPrice: 20_000_000 + }) + ); + } + + function test_insuranceFundAdlPositivePosition() public { + vm.prank(address(operatorManager)); + ledger.executeAdl({ + adl: EventTypes.Adl({ + accountId: BOB, + insuranceAccountId: INSURANCE_FUND, + symbolHash: SYMBOL_HASH_ETH_USDC, + positionQtyTransfer: 1_000_000_000, + costPositionTransfer: 100_000_000, + adlPrice: 30_000_000, + sumUnitaryFundings: 20_000_000_000_000_000, + timestamp: 0 + }), + eventId: 1 + }); + + AccountTypes.PerpPosition memory bobEthPosition = ledger.getPerpPosition(BOB, SYMBOL_HASH_ETH_USDC); + AccountTypes.PerpPosition memory insuranceFundEthPosition = + ledger.getPerpPosition(INSURANCE_FUND, SYMBOL_HASH_ETH_USDC); + + assertEq(bobEthPosition.costPosition, 100_000_000); + assertEq(bobEthPosition.lastExecutedPrice, 30_000_000); + assertEq(bobEthPosition.lastAdlPrice, 30_000_000); + assertEq(bobEthPosition.lastSumUnitaryFundings, 20_000_000_000_000_000); + + assertEq(bobEthPosition.positionQty, 0); + assertEq(bobEthPosition.averageEntryPrice, 0); + assertEq(ledger.getUserLedgerLastEngineEventId(BOB), 1); + + assertEq(insuranceFundEthPosition.costPosition, 200_000_000); + assertEq(insuranceFundEthPosition.lastExecutedPrice, 30_000_000); + assertEq(insuranceFundEthPosition.lastAdlPrice, 30_000_000); + assertEq(insuranceFundEthPosition.lastSumUnitaryFundings, 20_000_000_000_000_000); + + assertEq(insuranceFundEthPosition.positionQty, 1_000_000_000); + // assertEq(insuranceFundEthPosition.averageEntryPrice, 0); + assertEq(ledger.getUserLedgerLastEngineEventId(INSURANCE_FUND), 1); + } + + function test_insuranceFundAdlNegativePosition() public { + vm.prank(address(operatorManager)); + ledger.executeAdl({ + adl: EventTypes.Adl({ + accountId: BOB, + insuranceAccountId: INSURANCE_FUND, + symbolHash: SYMBOL_HASH_BTC_USDC, + positionQtyTransfer: -1_000_000_000, + costPositionTransfer: -100_000_000, + adlPrice: 40_000_000, + sumUnitaryFundings: 20_000_000_000_000_000, + timestamp: 0 + }), + eventId: 1 + }); + + AccountTypes.PerpPosition memory bobBtcPosition = ledger.getPerpPosition(BOB, SYMBOL_HASH_BTC_USDC); + AccountTypes.PerpPosition memory insuranceFundBtcPosition = + ledger.getPerpPosition(INSURANCE_FUND, SYMBOL_HASH_BTC_USDC); + + assertEq(bobBtcPosition.costPosition, 100_000_000); + assertEq(bobBtcPosition.lastExecutedPrice, 40_000_000); + assertEq(bobBtcPosition.lastAdlPrice, 40_000_000); + assertEq(bobBtcPosition.lastSumUnitaryFundings, 20_000_000_000_000_000); + + assertEq(bobBtcPosition.positionQty, 0); + assertEq(bobBtcPosition.averageEntryPrice, 0); + assertEq(ledger.getUserLedgerLastEngineEventId(BOB), 1); + + assertEq(insuranceFundBtcPosition.costPosition, 0); + assertEq(insuranceFundBtcPosition.lastExecutedPrice, 40_000_000); + assertEq(insuranceFundBtcPosition.lastAdlPrice, 40_000_000); + assertEq(insuranceFundBtcPosition.lastSumUnitaryFundings, 20_000_000_000_000_000); + + assertEq(insuranceFundBtcPosition.positionQty, -1_000_000_000); + // assertEq(insuranceFundEthPosition.averageEntryPrice, 0); + assertEq(ledger.getUserLedgerLastEngineEventId(INSURANCE_FUND), 1); + } + + function testRevert_insuranceFundAdlLessPosition() public { + vm.prank(address(operatorManager)); + vm.expectRevert(abi.encodeWithSelector(IError.InsurancePositionQtyInvalid.selector, 1000000000, -500000000)); + ledger.executeAdl({ + adl: EventTypes.Adl({ + accountId: CHARLIE, + insuranceAccountId: INSURANCE_FUND, + symbolHash: SYMBOL_HASH_ETH_USDC, + positionQtyTransfer: 1_000_000_000, + costPositionTransfer: -100_000_000, + adlPrice: 40_000_000, + sumUnitaryFundings: 20_000_000_000_000_000, + timestamp: 0 + }), + eventId: 1 + }); + } + + function testRevert_insuranceFundAdlEmptyPosition() public { + vm.prank(address(operatorManager)); + vm.expectRevert( + abi.encodeWithSelector( + IError.UserPerpPositionQtyZero.selector, + 0xa11ce00000000000000000000000000000000000000000000000000000000000, + 0x2222101010101010101010101010101010101010101010101010101010101010 + ) + ); + ledger.executeAdl({ + adl: EventTypes.Adl({ + accountId: ALICE, + insuranceAccountId: INSURANCE_FUND, + symbolHash: SYMBOL_HASH_ETH_USDC, + positionQtyTransfer: 1_000_000_000, + costPositionTransfer: -100_000_000, + adlPrice: 40_000_000, + sumUnitaryFundings: 20_000_000_000_000_000, + timestamp: 0 + }), + eventId: 1 + }); + } +} diff --git a/test/functionTest/Liquidation.t.sol b/test/functionTest/Liquidation.t.sol new file mode 100644 index 0000000..859ccbb --- /dev/null +++ b/test/functionTest/Liquidation.t.sol @@ -0,0 +1,489 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.19; + +import "forge-std/Test.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; +import "../../src/OperatorManager.sol"; +import "../../src/VaultManager.sol"; +import "../../src/MarketManager.sol"; +import "../mock/LedgerCrossChainManagerMock.sol"; +import "../../src/FeeManager.sol"; +import "../cheater/LedgerCheater.sol"; +import "../../src/LedgerImplA.sol"; + +contract LiquidationTest is Test { + bytes32 constant BROKER_HASH = 0x083098c593f395bea1de45dda552d9f14e8fcb0be3faaa7a1903c5477d7ba7fd; + bytes32 constant TOKEN_HASH = 0xd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa; + bytes32 constant SYMBOL_HASH_BTC_USDC = 0x1111101010101010101010101010101010101010101010101010101010101010; + bytes32 constant SYMBOL_HASH_ETH_USDC = 0x2222101010101010101010101010101010101010101010101010101010101010; + uint256 constant CHAIN_ID = 986532; + bytes32 constant LIQUIDATED_ACCOUNT_ID = 0xa11ce00000000000000000000000000000000000000000000000000000000000; + bytes32 constant LIQUIDATOR_ACCOUNT_ID = 0xb0b0000000000000000000000000000000000000000000000000000000000000; + bytes32 constant INSURANCE_FUND = 0x1234123412341234123412341234123412341234123412341234123412341234; + + ProxyAdmin admin; + address constant operatorAddress = address(0x1234567890); + LedgerCrossChainManagerMock ledgerCrossChainManager; + IOperatorManager operatorManager; + IVaultManager vaultManager; + LedgerCheater ledger; + IFeeManager feeManager; + IMarketManager marketManager; + TransparentUpgradeableProxy operatorProxy; + TransparentUpgradeableProxy vaultProxy; + TransparentUpgradeableProxy ledgerProxy; + TransparentUpgradeableProxy feeProxy; + TransparentUpgradeableProxy marketProxy; + + function setUp() public { + admin = new ProxyAdmin(); + + ledgerCrossChainManager = new LedgerCrossChainManagerMock(); + + IOperatorManager operatorManagerImpl = new OperatorManager(); + IVaultManager vaultManagerImpl = new VaultManager(); + ILedger ledgerImpl = new LedgerCheater(); + IFeeManager feeImpl = new FeeManager(); + IMarketManager marketImpl = new MarketManager(); + LedgerImplA ledgerImplA = new LedgerImplA(); + + bytes memory initData = abi.encodeWithSignature("initialize()"); + operatorProxy = new TransparentUpgradeableProxy(address(operatorManagerImpl), address(admin), initData); + vaultProxy = new TransparentUpgradeableProxy(address(vaultManagerImpl), address(admin), initData); + ledgerProxy = new TransparentUpgradeableProxy(address(ledgerImpl), address(admin), initData); + feeProxy = new TransparentUpgradeableProxy(address(feeImpl), address(admin), initData); + marketProxy = new TransparentUpgradeableProxy(address(marketImpl), address(admin), initData); + + operatorManager = IOperatorManager(address(operatorProxy)); + vaultManager = IVaultManager(address(vaultProxy)); + ledger = LedgerCheater(address(ledgerProxy)); + feeManager = IFeeManager(address(feeProxy)); + marketManager = IMarketManager(address(marketProxy)); + + ledger.setOperatorManagerAddress(address(operatorManager)); + ledger.setCrossChainManager(address(ledgerCrossChainManager)); + ledger.setVaultManager(address(vaultManager)); + ledger.setFeeManager(address(feeManager)); + ledger.setMarketManager(address(marketManager)); + ledger.setLedgerImplA(address(ledgerImplA)); + + operatorManager.setOperator(operatorAddress); + operatorManager.setLedger(address(ledger)); + + vaultManager.setLedgerAddress(address(ledger)); + if (!vaultManager.getAllowedToken(TOKEN_HASH)) { + vaultManager.setAllowedToken(TOKEN_HASH, true); + } + if (!vaultManager.getAllowedBroker(BROKER_HASH)) { + vaultManager.setAllowedBroker(BROKER_HASH, true); + } + if (!vaultManager.getAllowedSymbol(SYMBOL_HASH_BTC_USDC)) { + vaultManager.setAllowedSymbol(SYMBOL_HASH_BTC_USDC, true); + } + if (!vaultManager.getAllowedSymbol(SYMBOL_HASH_ETH_USDC)) { + vaultManager.setAllowedSymbol(SYMBOL_HASH_ETH_USDC, true); + } + vaultManager.setAllowedChainToken(TOKEN_HASH, CHAIN_ID, true); + + feeManager.setLedgerAddress(address(ledger)); + + marketManager.setOperatorManagerAddress(address(operatorManager)); + marketManager.setLedgerAddress(address(ledger)); + marketManager.setPerpMarketCfg( + SYMBOL_HASH_BTC_USDC, + MarketTypes.PerpMarketCfg({ + baseMaintenanceMargin: 1, + baseInitialMargin: 1, + liquidationFeeMax: 1, + markPrice: 1, + indexPriceOrderly: 1, + sumUnitaryFundings: 1, + lastMarkPriceUpdated: 1, + lastFundingUpdated: 1 + }) + ); + + ledgerCrossChainManager.setLedger(address(ledger)); + ledgerCrossChainManager.setOperatorManager(address(operatorManager)); + } + + function testLiquidation() public { + ledger.cheatDeposit(LIQUIDATED_ACCOUNT_ID, TOKEN_HASH, 400000000, CHAIN_ID); + ledger.cheatDeposit(LIQUIDATOR_ACCOUNT_ID, TOKEN_HASH, 100000000000, CHAIN_ID); + ledger.cheatDeposit(INSURANCE_FUND, TOKEN_HASH, 1000000000000, CHAIN_ID); + + ledger.cheatSetUserPosition( + LIQUIDATED_ACCOUNT_ID, + SYMBOL_HASH_BTC_USDC, + AccountTypes.PerpPosition({ + positionQty: -400000000, + costPosition: -8000000, + lastSumUnitaryFundings: 2000000000000002, + lastExecutedPrice: 200000000, + lastSettledPrice: 0, + averageEntryPrice: 0, + openingCost: 0, + lastAdlPrice: 0 + }) + ); + ledger.cheatSetUserPosition( + LIQUIDATED_ACCOUNT_ID, + SYMBOL_HASH_ETH_USDC, + AccountTypes.PerpPosition({ + positionQty: 200000000, + costPosition: 3600000000, + lastSumUnitaryFundings: 2000000000000002, + lastExecutedPrice: 180000000000, + lastSettledPrice: 0, + averageEntryPrice: 0, + openingCost: 0, + lastAdlPrice: 0 + }) + ); + + vm.prank(address(operatorManager)); + + EventTypes.LiquidationTransfer[] memory liquidationTransfers = new EventTypes.LiquidationTransfer[](2); + liquidationTransfers[0] = EventTypes.LiquidationTransfer({ + liquidationTransferId: 100, + liquidatorAccountId: LIQUIDATOR_ACCOUNT_ID, + symbolHash: SYMBOL_HASH_BTC_USDC, + positionQtyTransfer: -200000000, + costPositionTransfer: -4000000, + liquidatorFee: 15000, + insuranceFee: 15000, + liquidationFee: 30000, + markPrice: 200000000, + sumUnitaryFundings: 3000000000000003 + }); + liquidationTransfers[1] = EventTypes.LiquidationTransfer({ + liquidationTransferId: 101, + liquidatorAccountId: LIQUIDATOR_ACCOUNT_ID, + symbolHash: SYMBOL_HASH_ETH_USDC, + positionQtyTransfer: 100000000, + costPositionTransfer: 1800000000, + liquidatorFee: 18000000, + insuranceFee: 18000000, + liquidationFee: 36000000, + markPrice: 180000000000, + sumUnitaryFundings: 3000000000000003 + }); + + ledger.executeLiquidation({ + liquidation: EventTypes.Liquidation({ + liquidatedAccountId: LIQUIDATED_ACCOUNT_ID, + insuranceAccountId: INSURANCE_FUND, + insuranceTransferAmount: 0, + liquidatedAssetHash: TOKEN_HASH, + liquidationTransfers: liquidationTransfers, + timestamp: 1672811225658 + }), + eventId: 12000 + }); + + AccountTypes.PerpPosition memory LiquidatedBtcPosition = + ledger.getPerpPosition(LIQUIDATED_ACCOUNT_ID, SYMBOL_HASH_BTC_USDC); + assertEq(LiquidatedBtcPosition.positionQty, -200000000); + assertEq(LiquidatedBtcPosition.costPosition, -7970000); + assertEq(LiquidatedBtcPosition.lastSumUnitaryFundings, 3000000000000003); + assertEq(LiquidatedBtcPosition.lastExecutedPrice, 200000000); + + AccountTypes.PerpPosition memory LiquidatedEthPosition = + ledger.getPerpPosition(LIQUIDATED_ACCOUNT_ID, SYMBOL_HASH_ETH_USDC); + assertEq(LiquidatedEthPosition.positionQty, 100000000); + assertEq(LiquidatedEthPosition.costPosition, 1838000001); + assertEq(LiquidatedEthPosition.lastSumUnitaryFundings, 3000000000000003); + assertEq(LiquidatedEthPosition.lastExecutedPrice, 180000000000); + + uint128 liquidatedTokenBalance = ledger.getUserLedgerBalance(LIQUIDATED_ACCOUNT_ID, TOKEN_HASH); + assertEq(liquidatedTokenBalance, 400000000); + + AccountTypes.PerpPosition memory LiquidatorBtcPosition = + ledger.getPerpPosition(LIQUIDATOR_ACCOUNT_ID, SYMBOL_HASH_BTC_USDC); + assertEq(LiquidatorBtcPosition.positionQty, -200000000); + assertEq(LiquidatorBtcPosition.costPosition, -4015000); + assertEq(LiquidatorBtcPosition.lastSumUnitaryFundings, 3000000000000003); + assertEq(LiquidatorBtcPosition.lastExecutedPrice, 200000000); + + AccountTypes.PerpPosition memory LiquidatorEthPosition = + ledger.getPerpPosition(LIQUIDATOR_ACCOUNT_ID, SYMBOL_HASH_ETH_USDC); + assertEq(LiquidatorEthPosition.positionQty, 100000000); + assertEq(LiquidatorEthPosition.costPosition, 1782000000); + assertEq(LiquidatorEthPosition.lastSumUnitaryFundings, 3000000000000003); + assertEq(LiquidatorEthPosition.lastExecutedPrice, 180000000000); + + uint128 liquidatorTokenBalance = ledger.getUserLedgerBalance(LIQUIDATOR_ACCOUNT_ID, TOKEN_HASH); + assertEq(liquidatorTokenBalance, 100000000000); + + AccountTypes.PerpPosition memory InsuranceBtcPosition = + ledger.getPerpPosition(INSURANCE_FUND, SYMBOL_HASH_BTC_USDC); + assertEq(InsuranceBtcPosition.positionQty, -0); + assertEq(InsuranceBtcPosition.costPosition, -15000); + assertEq(InsuranceBtcPosition.lastSumUnitaryFundings, 3000000000000003); + assertEq(InsuranceBtcPosition.lastExecutedPrice, 200000000); + + AccountTypes.PerpPosition memory InsuranceEthPosition = + ledger.getPerpPosition(INSURANCE_FUND, SYMBOL_HASH_ETH_USDC); + assertEq(InsuranceEthPosition.positionQty, 0); + assertEq(InsuranceEthPosition.costPosition, -18000000); + assertEq(InsuranceEthPosition.lastSumUnitaryFundings, 3000000000000003); + assertEq(InsuranceEthPosition.lastExecutedPrice, 180000000000); + + uint128 insuranceTokenBalance = ledger.getUserLedgerBalance(INSURANCE_FUND, TOKEN_HASH); + assertEq(insuranceTokenBalance, 1000000000000); + } + + function testLiquidatorIsInsuranceFund() public { + ledger.cheatDeposit(LIQUIDATED_ACCOUNT_ID, TOKEN_HASH, 1000, CHAIN_ID); + ledger.cheatDeposit(INSURANCE_FUND, TOKEN_HASH, 1000000000000, CHAIN_ID); + + ledger.cheatSetUserPosition( + LIQUIDATED_ACCOUNT_ID, + SYMBOL_HASH_BTC_USDC, + AccountTypes.PerpPosition({ + positionQty: -400000000, + costPosition: -8000000, + lastSumUnitaryFundings: 3000000000000003, + lastExecutedPrice: 200000000, + lastSettledPrice: 0, + averageEntryPrice: 0, + openingCost: 0, + lastAdlPrice: 0 + }) + ); + ledger.cheatSetUserPosition( + LIQUIDATED_ACCOUNT_ID, + SYMBOL_HASH_ETH_USDC, + AccountTypes.PerpPosition({ + positionQty: 200000000, + costPosition: 3600000000, + lastSumUnitaryFundings: 3000000000000003, + lastExecutedPrice: 180000000000, + lastSettledPrice: 0, + averageEntryPrice: 0, + openingCost: 0, + lastAdlPrice: 0 + }) + ); + vm.prank(address(operatorManager)); + + EventTypes.LiquidationTransfer[] memory liquidationTransfers = new EventTypes.LiquidationTransfer[](2); + liquidationTransfers[0] = EventTypes.LiquidationTransfer({ + liquidationTransferId: 100, + liquidatorAccountId: INSURANCE_FUND, + symbolHash: SYMBOL_HASH_BTC_USDC, + positionQtyTransfer: -400000000, + costPositionTransfer: -8000000, + liquidatorFee: 0, + insuranceFee: 0, + liquidationFee: 0, + markPrice: 250000000, + sumUnitaryFundings: 3000000000000003 + }); + liquidationTransfers[1] = EventTypes.LiquidationTransfer({ + liquidationTransferId: 101, + liquidatorAccountId: INSURANCE_FUND, + symbolHash: SYMBOL_HASH_ETH_USDC, + positionQtyTransfer: 200000000, + costPositionTransfer: 3600000000, + liquidatorFee: 0, + insuranceFee: 0, + liquidationFee: 0, + markPrice: 150000000000, + sumUnitaryFundings: 3000000000000003 + }); + + ledger.executeLiquidation({ + liquidation: EventTypes.Liquidation({ + liquidatedAccountId: LIQUIDATED_ACCOUNT_ID, + insuranceAccountId: INSURANCE_FUND, + insuranceTransferAmount: 1000, + liquidatedAssetHash: TOKEN_HASH, + liquidationTransfers: liquidationTransfers, + timestamp: 1672811225658 + }), + eventId: 12000 + }); + + AccountTypes.PerpPosition memory LiquidatedBtcPosition = + ledger.getPerpPosition(LIQUIDATED_ACCOUNT_ID, SYMBOL_HASH_BTC_USDC); + assertEq(LiquidatedBtcPosition.positionQty, 0); + assertEq(LiquidatedBtcPosition.costPosition, 0); + assertEq(LiquidatedBtcPosition.lastSumUnitaryFundings, 0); + assertEq(LiquidatedBtcPosition.lastExecutedPrice, 0); + + AccountTypes.PerpPosition memory LiquidatedEthPosition = + ledger.getPerpPosition(LIQUIDATED_ACCOUNT_ID, SYMBOL_HASH_ETH_USDC); + assertEq(LiquidatedEthPosition.positionQty, 0); + assertEq(LiquidatedEthPosition.costPosition, 0); + assertEq(LiquidatedEthPosition.lastSumUnitaryFundings, 0); + assertEq(LiquidatedEthPosition.lastExecutedPrice, 0); + + uint128 liquidatedTokenBalance = ledger.getUserLedgerBalance(LIQUIDATED_ACCOUNT_ID, TOKEN_HASH); + assertEq(liquidatedTokenBalance, 0); + + AccountTypes.PerpPosition memory LiquidatorBtcPosition = + ledger.getPerpPosition(INSURANCE_FUND, SYMBOL_HASH_BTC_USDC); + assertEq(LiquidatorBtcPosition.positionQty, -400000000); + assertEq(LiquidatorBtcPosition.costPosition, -8000000); + assertEq(LiquidatorBtcPosition.lastSumUnitaryFundings, 3000000000000003); + assertEq(LiquidatorBtcPosition.lastExecutedPrice, 250000000); + + AccountTypes.PerpPosition memory LiquidatorEthPosition = + ledger.getPerpPosition(INSURANCE_FUND, SYMBOL_HASH_ETH_USDC); + assertEq(LiquidatorEthPosition.positionQty, 200000000); + assertEq(LiquidatorEthPosition.costPosition, 3600000000); + assertEq(LiquidatorEthPosition.lastSumUnitaryFundings, 3000000000000003); + assertEq(LiquidatorEthPosition.lastExecutedPrice, 150000000000); + + uint128 liquidatorTokenBalance = ledger.getUserLedgerBalance(INSURANCE_FUND, TOKEN_HASH); + assertEq(liquidatorTokenBalance, 1000000001000); + } + + function testLiquidatorIsInsuranceFundTwoStages() public { + ledger.cheatDeposit(LIQUIDATED_ACCOUNT_ID, TOKEN_HASH, 1000, CHAIN_ID); + ledger.cheatDeposit(INSURANCE_FUND, TOKEN_HASH, 1000000000000, CHAIN_ID); + + ledger.cheatSetUserPosition( + LIQUIDATED_ACCOUNT_ID, + SYMBOL_HASH_BTC_USDC, + AccountTypes.PerpPosition({ + positionQty: -400000000, + costPosition: -8000000, + lastSumUnitaryFundings: 1000000000000001, + lastExecutedPrice: 200000000, + lastSettledPrice: 0, + averageEntryPrice: 0, + openingCost: 0, + lastAdlPrice: 0 + }) + ); + ledger.cheatSetUserPosition( + LIQUIDATED_ACCOUNT_ID, + SYMBOL_HASH_ETH_USDC, + AccountTypes.PerpPosition({ + positionQty: 200000000, + costPosition: 3600000000, + lastSumUnitaryFundings: 1000000000000001, + lastExecutedPrice: 180000000000, + lastSettledPrice: 0, + averageEntryPrice: 0, + openingCost: 0, + lastAdlPrice: 0 + }) + ); + vm.prank(address(operatorManager)); + + EventTypes.LiquidationTransfer[] memory firstLiquidationTransfers = new EventTypes.LiquidationTransfer[](2); + firstLiquidationTransfers[0] = EventTypes.LiquidationTransfer({ + liquidationTransferId: 100, + liquidatorAccountId: INSURANCE_FUND, + symbolHash: SYMBOL_HASH_BTC_USDC, + positionQtyTransfer: -200000000, + costPositionTransfer: -2000000, + liquidatorFee: 0, + insuranceFee: 0, + liquidationFee: 0, + markPrice: 250000000, + sumUnitaryFundings: 2000000000000002 + }); + firstLiquidationTransfers[1] = EventTypes.LiquidationTransfer({ + liquidationTransferId: 101, + liquidatorAccountId: INSURANCE_FUND, + symbolHash: SYMBOL_HASH_ETH_USDC, + positionQtyTransfer: 100000000, + costPositionTransfer: 1800000000, + liquidatorFee: 0, + insuranceFee: 0, + liquidationFee: 0, + markPrice: 150000000000, + sumUnitaryFundings: 2000000000000002 + }); + + ledger.executeLiquidation({ + liquidation: EventTypes.Liquidation({ + liquidatedAccountId: LIQUIDATED_ACCOUNT_ID, + insuranceAccountId: INSURANCE_FUND, + insuranceTransferAmount: 500, + liquidatedAssetHash: TOKEN_HASH, + liquidationTransfers: firstLiquidationTransfers, + timestamp: 1672811225658 + }), + eventId: 12000 + }); + + vm.prank(address(operatorManager)); + EventTypes.LiquidationTransfer[] memory secondLiquidationTransfers = new EventTypes.LiquidationTransfer[](2); + secondLiquidationTransfers[0] = EventTypes.LiquidationTransfer({ + liquidationTransferId: 100, + liquidatorAccountId: INSURANCE_FUND, + symbolHash: SYMBOL_HASH_BTC_USDC, + positionQtyTransfer: -200000000, + costPositionTransfer: -4000000, + liquidatorFee: 0, + insuranceFee: 0, + liquidationFee: 0, + markPrice: 250000000, + sumUnitaryFundings: 3000000000000003 + }); + secondLiquidationTransfers[1] = EventTypes.LiquidationTransfer({ + liquidationTransferId: 101, + liquidatorAccountId: INSURANCE_FUND, + symbolHash: SYMBOL_HASH_ETH_USDC, + positionQtyTransfer: 100000000, + costPositionTransfer: 1800000000, + liquidatorFee: 0, + insuranceFee: 0, + liquidationFee: 0, + markPrice: 150000000000, + sumUnitaryFundings: 3000000000000003 + }); + + ledger.executeLiquidation({ + liquidation: EventTypes.Liquidation({ + liquidatedAccountId: LIQUIDATED_ACCOUNT_ID, + insuranceAccountId: INSURANCE_FUND, + insuranceTransferAmount: 500, + liquidatedAssetHash: TOKEN_HASH, + liquidationTransfers: secondLiquidationTransfers, + timestamp: 1672811225658 + }), + eventId: 12001 + }); + + AccountTypes.PerpPosition memory LiquidatedBtcPosition = + ledger.getPerpPosition(LIQUIDATED_ACCOUNT_ID, SYMBOL_HASH_BTC_USDC); + assertEq(LiquidatedBtcPosition.positionQty, 0); + assertEq(LiquidatedBtcPosition.costPosition, -8000000); + assertEq(LiquidatedBtcPosition.lastSumUnitaryFundings, 3000000000000003); + assertEq(LiquidatedBtcPosition.lastExecutedPrice, 250000000); + + AccountTypes.PerpPosition memory LiquidatedEthPosition = + ledger.getPerpPosition(LIQUIDATED_ACCOUNT_ID, SYMBOL_HASH_ETH_USDC); + assertEq(LiquidatedEthPosition.positionQty, 0); + assertEq(LiquidatedEthPosition.costPosition, 3000002); + assertEq(LiquidatedEthPosition.lastSumUnitaryFundings, 3000000000000003); + assertEq(LiquidatedEthPosition.lastExecutedPrice, 150000000000); + + uint128 liquidatedTokenBalance = ledger.getUserLedgerBalance(LIQUIDATED_ACCOUNT_ID, TOKEN_HASH); + assertEq(liquidatedTokenBalance, 0); + + AccountTypes.PerpPosition memory LiquidatorBtcPosition = + ledger.getPerpPosition(INSURANCE_FUND, SYMBOL_HASH_BTC_USDC); + assertEq(LiquidatorBtcPosition.positionQty, -400000000); + assertEq(LiquidatorBtcPosition.costPosition, -8000000); + assertEq(LiquidatorBtcPosition.lastSumUnitaryFundings, 3000000000000003); + assertEq(LiquidatorBtcPosition.lastExecutedPrice, 250000000); + + AccountTypes.PerpPosition memory LiquidatorEthPosition = + ledger.getPerpPosition(INSURANCE_FUND, SYMBOL_HASH_ETH_USDC); + assertEq(LiquidatorEthPosition.positionQty, 200000000); + assertEq(LiquidatorEthPosition.costPosition, 3601000001); + assertEq(LiquidatorEthPosition.lastSumUnitaryFundings, 3000000000000003); + assertEq(LiquidatorEthPosition.lastExecutedPrice, 150000000000); + + uint128 liquidatorTokenBalance = ledger.getUserLedgerBalance(INSURANCE_FUND, TOKEN_HASH); + assertEq(liquidatorTokenBalance, 1000000001000); + } +} diff --git a/test/functionTest/PerpTradeUpload.sol b/test/functionTest/PerpTradeUpload.sol new file mode 100644 index 0000000..7e0069d --- /dev/null +++ b/test/functionTest/PerpTradeUpload.sol @@ -0,0 +1,328 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.19; + +import "forge-std/Test.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; +import "../../src/OperatorManager.sol"; +import "../../src/VaultManager.sol"; +import "../../src/MarketManager.sol"; +import "../../src/FeeManager.sol"; +import "../mock/LedgerCrossChainManagerMock.sol"; +import "../cheater/LedgerCheater.sol"; +import "../../src/LedgerImplA.sol"; + +contract PerpTradeUploadTest is Test { + bytes32 constant BROKER_HASH = 0x083098c593f395bea1de45dda552d9f14e8fcb0be3faaa7a1903c5477d7ba7fd; + bytes32 constant TOKEN_HASH = 0xd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa; + bytes32 constant SYMBOL_HASH_ETH_USDC = 0x2222101010101010101010101010101010101010101010101010101010101010; + uint256 constant CHAIN_ID = 986532; + bytes32 constant ALICE = 0xa11ce00000000000000000000000000000000000000000000000000000000000; + bytes32 constant BOB = 0xb0b0000000000000000000000000000000000000000000000000000000000000; + bytes32 constant INSURANCE_FUND = 0x1234123412341234123412341234123412341234123412341234123412341234; + + ProxyAdmin admin; + address constant operatorAddress = address(0x1234567890); + LedgerCrossChainManagerMock ledgerCrossChainManager; + IOperatorManager operatorManager; + IVaultManager vaultManager; + LedgerCheater ledger; + IFeeManager feeManager; + IMarketManager marketManager; + TransparentUpgradeableProxy operatorProxy; + TransparentUpgradeableProxy vaultProxy; + TransparentUpgradeableProxy ledgerProxy; + TransparentUpgradeableProxy feeProxy; + TransparentUpgradeableProxy marketProxy; + + function setUp() public { + admin = new ProxyAdmin(); + + ledgerCrossChainManager = new LedgerCrossChainManagerMock(); + + IOperatorManager operatorManagerImpl = new OperatorManager(); + IVaultManager vaultManagerImpl = new VaultManager(); + ILedger ledgerImpl = new LedgerCheater(); + IFeeManager feeImpl = new FeeManager(); + IMarketManager marketImpl = new MarketManager(); + LedgerImplA ledgerImplA = new LedgerImplA(); + + bytes memory initData = abi.encodeWithSignature("initialize()"); + operatorProxy = new TransparentUpgradeableProxy(address(operatorManagerImpl), address(admin), initData); + vaultProxy = new TransparentUpgradeableProxy(address(vaultManagerImpl), address(admin), initData); + ledgerProxy = new TransparentUpgradeableProxy(address(ledgerImpl), address(admin), initData); + feeProxy = new TransparentUpgradeableProxy(address(feeImpl), address(admin), initData); + marketProxy = new TransparentUpgradeableProxy(address(marketImpl), address(admin), initData); + + operatorManager = IOperatorManager(address(operatorProxy)); + vaultManager = IVaultManager(address(vaultProxy)); + ledger = LedgerCheater(address(ledgerProxy)); + feeManager = IFeeManager(address(feeProxy)); + marketManager = IMarketManager(address(marketProxy)); + + ledger.setOperatorManagerAddress(address(operatorManager)); + ledger.setCrossChainManager(address(ledgerCrossChainManager)); + ledger.setVaultManager(address(vaultManager)); + ledger.setFeeManager(address(feeManager)); + ledger.setMarketManager(address(marketManager)); + ledger.setLedgerImplA(address(ledgerImplA)); + + operatorManager.setOperator(operatorAddress); + operatorManager.setLedger(address(ledger)); + + vaultManager.setLedgerAddress(address(ledger)); + if (!vaultManager.getAllowedToken(TOKEN_HASH)) { + vaultManager.setAllowedToken(TOKEN_HASH, true); + } + if (!vaultManager.getAllowedBroker(BROKER_HASH)) { + vaultManager.setAllowedBroker(BROKER_HASH, true); + } + vaultManager.setAllowedChainToken(TOKEN_HASH, CHAIN_ID, true); + vaultManager.setAllowedSymbol(SYMBOL_HASH_ETH_USDC, true); + + feeManager.setLedgerAddress(address(ledger)); + + marketManager.setOperatorManagerAddress(address(operatorManager)); + marketManager.setLedgerAddress(address(ledger)); + + ledgerCrossChainManager.setLedger(address(ledger)); + ledgerCrossChainManager.setOperatorManager(address(operatorManager)); + } + + function test_perp_trade_init_holdings_zero() public { + ledger.cheatDeposit(ALICE, TOKEN_HASH, 1000000000, CHAIN_ID); + ledger.cheatDeposit(BOB, TOKEN_HASH, 1000000000, CHAIN_ID); + + vm.prank(address(operatorManager)); + ledger.executeProcessValidatedFutures({ + trade: PerpTypes.FuturesTradeUpload({ + tradeId: 13, + matchId: 1678975214714862536, + accountId: ALICE, + symbolHash: SYMBOL_HASH_ETH_USDC, + side: true, + tradeQty: -200000000, + sumUnitaryFundings: 1000000000000001, + executedPrice: 250000000, + notional: -5000000, + fee: 7500, + feeAssetHash: TOKEN_HASH, + timestamp: 1678975214714 + }) + }); + + vm.prank(address(operatorManager)); + ledger.executeProcessValidatedFutures({ + trade: PerpTypes.FuturesTradeUpload({ + tradeId: 14, + matchId: 1678975214714862536, + accountId: BOB, + symbolHash: SYMBOL_HASH_ETH_USDC, + side: false, + tradeQty: 200000000, + sumUnitaryFundings: 1000000000000001, + executedPrice: 250000000, + notional: 5000000, + fee: 5000, + feeAssetHash: TOKEN_HASH, + timestamp: 1678975214714 + }) + }); + + AccountTypes.PerpPosition memory positionA = ledger.getPerpPosition(ALICE, SYMBOL_HASH_ETH_USDC); + AccountTypes.PerpPosition memory positionB = ledger.getPerpPosition(BOB, SYMBOL_HASH_ETH_USDC); + + assertEq(ledger.getUserLedgerBalance(ALICE, TOKEN_HASH), 1000000000); + assertEq(positionA.positionQty, -200000000); + assertEq(positionA.costPosition, -4992500); + assertEq(positionA.lastSumUnitaryFundings, 1000000000000001); + assertEq(positionA.lastExecutedPrice, 250000000); + + assertEq(ledger.getUserLedgerBalance(BOB, TOKEN_HASH), 1000000000); + assertEq(positionB.positionQty, 200000000); + assertEq(positionB.costPosition, 5005000); + assertEq(positionB.lastSumUnitaryFundings, 1000000000000001); + assertEq(positionB.lastExecutedPrice, 250000000); + } + + function test_perp_trade_increase_positions() public { + bytes32 feeCollectorHash = feeManager.getFeeCollector(IFeeManager.FeeCollectorType.FuturesFeeCollector); + ledger.cheatDeposit(ALICE, TOKEN_HASH, 999992500, CHAIN_ID); + ledger.cheatDeposit(BOB, TOKEN_HASH, 999995000, CHAIN_ID); + ledger.cheatDeposit(feeCollectorHash, TOKEN_HASH, 12500, CHAIN_ID); + ledger.cheatSetUserPosition( + ALICE, + SYMBOL_HASH_ETH_USDC, + AccountTypes.PerpPosition({ + positionQty: -200000000, + costPosition: -5000000, + lastSumUnitaryFundings: 1000000000000001, + lastExecutedPrice: 250000000, + lastSettledPrice: 0, + averageEntryPrice: 0, + openingCost: 0, + lastAdlPrice: 0 + }) + ); + ledger.cheatSetUserPosition( + BOB, + SYMBOL_HASH_ETH_USDC, + AccountTypes.PerpPosition({ + positionQty: 200000000, + costPosition: 5000000, + lastSumUnitaryFundings: 1000000000000001, + lastExecutedPrice: 250000000, + lastSettledPrice: 0, + averageEntryPrice: 0, + openingCost: 0, + lastAdlPrice: 0 + }) + ); + + vm.prank(address(operatorManager)); + ledger.executeProcessValidatedFutures({ + trade: PerpTypes.FuturesTradeUpload({ + tradeId: 13, + matchId: 1678975214714862536, + accountId: ALICE, + symbolHash: SYMBOL_HASH_ETH_USDC, + side: true, + tradeQty: -200000000, + sumUnitaryFundings: 2000000000000002, + executedPrice: 250000000, + notional: -5000000, + fee: 7500, + feeAssetHash: TOKEN_HASH, + timestamp: 1678975214714 + }) + }); + + vm.prank(address(operatorManager)); + ledger.executeProcessValidatedFutures({ + trade: PerpTypes.FuturesTradeUpload({ + tradeId: 14, + matchId: 1678975214714862536, + accountId: BOB, + symbolHash: SYMBOL_HASH_ETH_USDC, + side: false, + tradeQty: 200000000, + sumUnitaryFundings: 2000000000000002, + executedPrice: 250000000, + notional: 5000000, + fee: 5000, + feeAssetHash: TOKEN_HASH, + timestamp: 1678975214714 + }) + }); + + AccountTypes.PerpPosition memory positionA = ledger.getPerpPosition(ALICE, SYMBOL_HASH_ETH_USDC); + AccountTypes.PerpPosition memory positionB = ledger.getPerpPosition(BOB, SYMBOL_HASH_ETH_USDC); + + assertEq(ledger.getUserLedgerBalance(ALICE, TOKEN_HASH), 999992500); + assertEq(positionA.positionQty, -400000000); + assertEq(positionA.costPosition, -11992500); + assertEq(positionA.lastSumUnitaryFundings, 2000000000000002); + assertEq(positionA.lastExecutedPrice, 250000000); + + assertEq(ledger.getUserLedgerBalance(BOB, TOKEN_HASH), 999995000); + assertEq(positionB.positionQty, 400000000); + assertEq(positionB.costPosition, 12005001); + assertEq(positionB.lastSumUnitaryFundings, 2000000000000002); + assertEq(positionB.lastExecutedPrice, 250000000); + + assertEq(ledger.getUserLedgerBalance(feeCollectorHash, TOKEN_HASH), 12500); + } + + function test_perp_trade_decrease_positions() public { + ledger.cheatDeposit(ALICE, TOKEN_HASH, 999985000, CHAIN_ID); + ledger.cheatDeposit(BOB, TOKEN_HASH, 999990000, CHAIN_ID); + bytes32 feeCollectorHash = feeManager.getFeeCollector(IFeeManager.FeeCollectorType.FuturesFeeCollector); + ledger.cheatDeposit(feeCollectorHash, TOKEN_HASH, 25000, CHAIN_ID); + ledger.cheatSetUserPosition( + ALICE, + SYMBOL_HASH_ETH_USDC, + AccountTypes.PerpPosition({ + positionQty: -400000000, + costPosition: -8000000, + lastSumUnitaryFundings: 2000000000000002, + lastExecutedPrice: 250000000, + lastSettledPrice: 0, + averageEntryPrice: 0, + openingCost: 0, + lastAdlPrice: 0 + }) + ); + ledger.cheatSetUserPosition( + BOB, + SYMBOL_HASH_ETH_USDC, + AccountTypes.PerpPosition({ + positionQty: 400000000, + costPosition: 12000000, + lastSumUnitaryFundings: 2000000000000002, + lastExecutedPrice: 250000000, + lastSettledPrice: 0, + averageEntryPrice: 0, + openingCost: 0, + lastAdlPrice: 0 + }) + ); + + vm.prank(address(operatorManager)); + ledger.executeProcessValidatedFutures({ + trade: PerpTypes.FuturesTradeUpload({ + tradeId: 13, + matchId: 1678975214714862536, + accountId: ALICE, + symbolHash: SYMBOL_HASH_ETH_USDC, + side: true, + tradeQty: 200000000, + sumUnitaryFundings: 3000000000000003, + executedPrice: 250000000, + notional: 5000000, + fee: 7500, + feeAssetHash: TOKEN_HASH, + timestamp: 1678975214714 + }) + }); + + vm.prank(address(operatorManager)); + ledger.executeProcessValidatedFutures({ + trade: PerpTypes.FuturesTradeUpload({ + tradeId: 14, + matchId: 1678975214714862536, + accountId: BOB, + symbolHash: SYMBOL_HASH_ETH_USDC, + side: false, + tradeQty: -200000000, + sumUnitaryFundings: 3000000000000003, + executedPrice: 250000000, + notional: -5000000, + fee: 5000, + feeAssetHash: TOKEN_HASH, + timestamp: 1678975214714 + }) + }); + + AccountTypes.PerpPosition memory positionA = ledger.getPerpPosition(ALICE, SYMBOL_HASH_ETH_USDC); + AccountTypes.PerpPosition memory positionB = ledger.getPerpPosition(BOB, SYMBOL_HASH_ETH_USDC); + + assertEq(ledger.getUserLedgerBalance(ALICE, TOKEN_HASH), 999985000); + assertEq(positionA.positionQty, -200000000); + assertEq(positionA.costPosition, -6992500); + assertEq(positionA.lastSumUnitaryFundings, 3000000000000003); + assertEq(positionA.lastExecutedPrice, 250000000); + + assertEq(ledger.getUserLedgerBalance(BOB, TOKEN_HASH), 999990000); + assertEq(positionB.positionQty, 200000000); + assertEq(positionB.costPosition, 11005001); + assertEq(positionB.lastSumUnitaryFundings, 3000000000000003); + assertEq(positionB.lastExecutedPrice, 250000000); + + AccountTypes.PerpPosition memory positionF = ledger.getPerpPosition(feeCollectorHash, SYMBOL_HASH_ETH_USDC); + assertEq(ledger.getUserLedgerBalance(feeCollectorHash, TOKEN_HASH), 25000); + assertEq(positionF.positionQty, 0); + assertEq(positionF.costPosition, -12500); + assertEq(positionF.lastSumUnitaryFundings, 3000000000000003); + assertEq(positionF.lastExecutedPrice, 0); + } +} diff --git a/test/functionTest/Settlement.t.sol b/test/functionTest/Settlement.t.sol new file mode 100644 index 0000000..ecb6f85 --- /dev/null +++ b/test/functionTest/Settlement.t.sol @@ -0,0 +1,338 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.19; + +import "forge-std/Test.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; +import "../../src/OperatorManager.sol"; +import "../../src/VaultManager.sol"; +import "../../src/MarketManager.sol"; +import "../../src/FeeManager.sol"; +import "../mock/LedgerCrossChainManagerMock.sol"; +import "../cheater/LedgerCheater.sol"; +import "../../src/LedgerImplA.sol"; + +contract SettlementTest is Test { + bytes32 constant BROKER_HASH = 0x083098c593f395bea1de45dda552d9f14e8fcb0be3faaa7a1903c5477d7ba7fd; + bytes32 constant TOKEN_HASH = 0xd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa; + bytes32 constant SYMBOL_HASH_BTC_USDC = 0x1111101010101010101010101010101010101010101010101010101010101010; + bytes32 constant SYMBOL_HASH_ETH_USDC = 0x2222101010101010101010101010101010101010101010101010101010101010; + uint256 constant CHAIN_ID = 986532; + bytes32 constant ALICE = 0xa11ce00000000000000000000000000000000000000000000000000000000000; + bytes32 constant BOB = 0xb0b0000000000000000000000000000000000000000000000000000000000000; + bytes32 constant INSURANCE_FUND = 0x1234123412341234123412341234123412341234123412341234123412341234; + + ProxyAdmin admin; + address constant operatorAddress = address(0x1234567890); + LedgerCrossChainManagerMock ledgerCrossChainManager; + IOperatorManager operatorManager; + IVaultManager vaultManager; + LedgerCheater ledger; + IFeeManager feeManager; + IMarketManager marketManager; + TransparentUpgradeableProxy operatorProxy; + TransparentUpgradeableProxy vaultProxy; + TransparentUpgradeableProxy ledgerProxy; + TransparentUpgradeableProxy feeProxy; + TransparentUpgradeableProxy marketProxy; + + function setUp() public { + admin = new ProxyAdmin(); + + ledgerCrossChainManager = new LedgerCrossChainManagerMock(); + + IOperatorManager operatorManagerImpl = new OperatorManager(); + IVaultManager vaultManagerImpl = new VaultManager(); + ILedger ledgerImpl = new LedgerCheater(); + IFeeManager feeImpl = new FeeManager(); + IMarketManager marketImpl = new MarketManager(); + LedgerImplA ledgerImplA = new LedgerImplA(); + + bytes memory initData = abi.encodeWithSignature("initialize()"); + operatorProxy = new TransparentUpgradeableProxy(address(operatorManagerImpl), address(admin), initData); + vaultProxy = new TransparentUpgradeableProxy(address(vaultManagerImpl), address(admin), initData); + ledgerProxy = new TransparentUpgradeableProxy(address(ledgerImpl), address(admin), initData); + feeProxy = new TransparentUpgradeableProxy(address(feeImpl), address(admin), initData); + marketProxy = new TransparentUpgradeableProxy(address(marketImpl), address(admin), initData); + + operatorManager = IOperatorManager(address(operatorProxy)); + vaultManager = IVaultManager(address(vaultProxy)); + ledger = LedgerCheater(address(ledgerProxy)); + feeManager = IFeeManager(address(feeProxy)); + marketManager = IMarketManager(address(marketProxy)); + + ledger.setOperatorManagerAddress(address(operatorManager)); + ledger.setCrossChainManager(address(ledgerCrossChainManager)); + ledger.setVaultManager(address(vaultManager)); + ledger.setFeeManager(address(feeManager)); + ledger.setMarketManager(address(marketManager)); + ledger.setLedgerImplA(address(ledgerImplA)); + + operatorManager.setOperator(operatorAddress); + operatorManager.setLedger(address(ledger)); + + vaultManager.setLedgerAddress(address(ledger)); + if (!vaultManager.getAllowedToken(TOKEN_HASH)) { + vaultManager.setAllowedToken(TOKEN_HASH, true); + } + if (!vaultManager.getAllowedBroker(BROKER_HASH)) { + vaultManager.setAllowedBroker(BROKER_HASH, true); + } + if (!vaultManager.getAllowedSymbol(SYMBOL_HASH_BTC_USDC)) { + vaultManager.setAllowedSymbol(SYMBOL_HASH_BTC_USDC, true); + } + if (!vaultManager.getAllowedSymbol(SYMBOL_HASH_ETH_USDC)) { + vaultManager.setAllowedSymbol(SYMBOL_HASH_ETH_USDC, true); + } + vaultManager.setAllowedChainToken(TOKEN_HASH, CHAIN_ID, true); + + feeManager.setLedgerAddress(address(ledger)); + + marketManager.setOperatorManagerAddress(address(operatorManager)); + marketManager.setLedgerAddress(address(ledger)); + + ledgerCrossChainManager.setLedger(address(ledger)); + ledgerCrossChainManager.setOperatorManager(address(operatorManager)); + + ledger.cheatDeposit(ALICE, TOKEN_HASH, 1_000_000_000, CHAIN_ID); + ledger.cheatDeposit(BOB, TOKEN_HASH, 1_000_000_000, CHAIN_ID); + ledger.cheatDeposit(INSURANCE_FUND, TOKEN_HASH, 10_000_000_000, CHAIN_ID); + + ledger.cheatSetUserPosition( + BOB, + SYMBOL_HASH_ETH_USDC, + AccountTypes.PerpPosition({ + positionQty: 1_000_000_000, + costPosition: 100_000_000, + lastSumUnitaryFundings: 10_000_000_000_000_000, + lastExecutedPrice: 20_000_000, + lastSettledPrice: 0, + averageEntryPrice: 0, + openingCost: 0, + lastAdlPrice: 0 + }) + ); + + ledger.cheatSetUserPosition( + BOB, + SYMBOL_HASH_BTC_USDC, + AccountTypes.PerpPosition({ + positionQty: 1_000_000_000, + costPosition: 100_000_000, + lastSumUnitaryFundings: 10_000_000_000_000_000, + lastExecutedPrice: 30_000_000, + lastSettledPrice: 0, + averageEntryPrice: 0, + openingCost: 0, + lastAdlPrice: 0 + }) + ); + } + + function test_settled_amount_zero() public { + EventTypes.SettlementExecution[] memory executions = new EventTypes.SettlementExecution[](0); + vm.prank(address(operatorManager)); + ledger.executeSettlement({ + settlement: EventTypes.Settlement({ + accountId: ALICE, + settledAmount: 0, + settledAssetHash: TOKEN_HASH, + insuranceAccountId: 0x0, + insuranceTransferAmount: 0, + settlementExecutions: executions, + timestamp: 0 + }), + eventId: 1 + }); + + assertEq(ledger.getUserLedgerBalance(ALICE, TOKEN_HASH), 1_000_000_000); + assertEq(ledger.getUserLedgerLastEngineEventId(ALICE), 1); + } + + function test_one_settlement_execution() public { + EventTypes.SettlementExecution[] memory executions = new EventTypes.SettlementExecution[](1); + executions[0] = EventTypes.SettlementExecution({ + symbolHash: SYMBOL_HASH_ETH_USDC, + sumUnitaryFundings: 20_000_000_000_000_000, + markPrice: 40_000_000, + settledAmount: 100_000_000 + }); + vm.prank(address(operatorManager)); + ledger.executeSettlement({ + settlement: EventTypes.Settlement({ + accountId: BOB, + settledAmount: 100_000_000, + settledAssetHash: TOKEN_HASH, + insuranceAccountId: 0x0, + insuranceTransferAmount: 0, + settlementExecutions: executions, + timestamp: 0 + }), + eventId: 1 + }); + + assertEq(ledger.getUserLedgerBalance(BOB, TOKEN_HASH), 1_100_000_000); + + AccountTypes.PerpPosition memory position = ledger.getPerpPosition(BOB, SYMBOL_HASH_ETH_USDC); + assertEq(position.costPosition, 300_000_000); + assertEq(position.lastExecutedPrice, 40_000_000); + assertEq(position.lastSumUnitaryFundings, 20_000_000_000_000_000); + assertEq(position.positionQty, 1_000_000_000); + + assertEq(ledger.getUserLedgerLastEngineEventId(BOB), 1); + } + + function test_two_settlement_execution() public { + EventTypes.SettlementExecution[] memory executions = new EventTypes.SettlementExecution[](2); + executions[0] = EventTypes.SettlementExecution({ + symbolHash: SYMBOL_HASH_ETH_USDC, + sumUnitaryFundings: 20_000_000_000_000_000, + markPrice: 40_000_000, + settledAmount: 200_000_000 + }); + executions[1] = EventTypes.SettlementExecution({ + symbolHash: SYMBOL_HASH_BTC_USDC, + sumUnitaryFundings: 20_000_000_000_000_000, + markPrice: 50_000_000, + settledAmount: 300_000_000 + }); + vm.prank(address(operatorManager)); + ledger.executeSettlement({ + settlement: EventTypes.Settlement({ + accountId: BOB, + settledAmount: 500_000_000, + settledAssetHash: TOKEN_HASH, + insuranceAccountId: 0x0, + insuranceTransferAmount: 0, + settlementExecutions: executions, + timestamp: 0 + }), + eventId: 1 + }); + + assertEq(ledger.getUserLedgerBalance(BOB, TOKEN_HASH), 1_500_000_000); + + AccountTypes.PerpPosition memory ethPosition = ledger.getPerpPosition(BOB, SYMBOL_HASH_ETH_USDC); + assertEq(ethPosition.costPosition, 400_000_000); + assertEq(ethPosition.lastExecutedPrice, 40_000_000); + assertEq(ethPosition.lastSumUnitaryFundings, 20_000_000_000_000_000); + assertEq(ethPosition.positionQty, 1_000_000_000); + + AccountTypes.PerpPosition memory btcPosition = ledger.getPerpPosition(BOB, SYMBOL_HASH_BTC_USDC); + assertEq(btcPosition.costPosition, 500_000_000); + assertEq(btcPosition.lastExecutedPrice, 50_000_000); + assertEq(btcPosition.lastSumUnitaryFundings, 20_000_000_000_000_000); + assertEq(btcPosition.positionQty, 1_000_000_000); + + assertEq(ledger.getUserLedgerLastEngineEventId(BOB), 1); + } + + function test_insurance_transfer_above_zero() public { + EventTypes.SettlementExecution[] memory executions = new EventTypes.SettlementExecution[](1); + executions[0] = EventTypes.SettlementExecution({ + symbolHash: SYMBOL_HASH_ETH_USDC, + sumUnitaryFundings: 20_000_000_000_000_000, + markPrice: 40_000_000, + settledAmount: -2_000_000_000 + }); + vm.prank(address(operatorManager)); + ledger.executeSettlement({ + settlement: EventTypes.Settlement({ + accountId: BOB, + settledAmount: -2_000_000_000, + settledAssetHash: TOKEN_HASH, + insuranceAccountId: INSURANCE_FUND, + insuranceTransferAmount: 1_000_000_000, + settlementExecutions: executions, + timestamp: 0 + }), + eventId: 1 + }); + + assertEq(ledger.getUserLedgerBalance(BOB, TOKEN_HASH), 0); + + AccountTypes.PerpPosition memory position = ledger.getPerpPosition(BOB, SYMBOL_HASH_ETH_USDC); + assertEq(position.costPosition, -1_800_000_000); + assertEq(position.lastExecutedPrice, 40_000_000); + assertEq(position.lastSumUnitaryFundings, 20_000_000_000_000_000); + assertEq(position.positionQty, 1_000_000_000); + + assertEq(ledger.getUserLedgerLastEngineEventId(BOB), 1); + } + + function testRevert_settled_amount_not_eq_sum() public { + EventTypes.SettlementExecution[] memory executions = new EventTypes.SettlementExecution[](1); + executions[0] = EventTypes.SettlementExecution({ + symbolHash: SYMBOL_HASH_ETH_USDC, + sumUnitaryFundings: 20_000_000_000_000_000, + markPrice: 40_000_000, + settledAmount: 2_000_000_000 + }); + vm.prank(address(operatorManager)); + vm.expectRevert(abi.encodeWithSelector(IError.TotalSettleAmountNotMatch.selector, 2_000_000_000)); + ledger.executeSettlement({ + settlement: EventTypes.Settlement({ + accountId: BOB, + settledAmount: 1_000_000_000, + settledAssetHash: TOKEN_HASH, + insuranceAccountId: 0x0, + insuranceTransferAmount: 0, + settlementExecutions: executions, + timestamp: 0 + }), + eventId: 1 + }); + } + + function testRevert_insurance_transfer_too_much() public { + EventTypes.SettlementExecution[] memory executions = new EventTypes.SettlementExecution[](1); + executions[0] = EventTypes.SettlementExecution({ + symbolHash: SYMBOL_HASH_ETH_USDC, + sumUnitaryFundings: 20_000_000_000_000_000, + markPrice: 40_000_000, + settledAmount: -2_000_000_000 + }); + vm.prank(address(operatorManager)); + vm.expectRevert( + abi.encodeWithSelector( + IError.InsuranceTransferAmountInvalid.selector, 1_000_000_000, 3_000_000_000, -2_000_000_000 + ) + ); + ledger.executeSettlement({ + settlement: EventTypes.Settlement({ + accountId: BOB, + settledAmount: -2_000_000_000, + settledAssetHash: TOKEN_HASH, + insuranceAccountId: INSURANCE_FUND, + insuranceTransferAmount: 3_000_000_000, + settlementExecutions: executions, + timestamp: 0 + }), + eventId: 1 + }); + } + + function testRevert_insurance_transfer_to_self() public { + EventTypes.SettlementExecution[] memory executions = new EventTypes.SettlementExecution[](1); + executions[0] = EventTypes.SettlementExecution({ + symbolHash: SYMBOL_HASH_ETH_USDC, + sumUnitaryFundings: 20_000_000_000_000_000, + markPrice: 40_000_000, + settledAmount: -2_000_000_000 + }); + vm.prank(address(operatorManager)); + vm.expectRevert(IError.InsuranceTransferToSelf.selector); + ledger.executeSettlement({ + settlement: EventTypes.Settlement({ + accountId: INSURANCE_FUND, + settledAmount: -2_000_000_000, + settledAssetHash: TOKEN_HASH, + insuranceAccountId: INSURANCE_FUND, + insuranceTransferAmount: 3_000_000_000, + settlementExecutions: executions, + timestamp: 0 + }), + eventId: 1 + }); + } +} diff --git a/test/functionTest/Signature.t.sol b/test/functionTest/Signature.t.sol new file mode 100644 index 0000000..b69cb0b --- /dev/null +++ b/test/functionTest/Signature.t.sol @@ -0,0 +1,682 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.19; + +import "forge-std/Test.sol"; +import "../../src/library/Signature.sol"; + +contract SignatureTest is Test { + address constant addr = 0x6a9961Ace9bF0C1B8B98ba11558A4125B1f5EA3f; + + // https://wootraders.atlassian.net/wiki/spaces/ORDER/pages/299009164/Test+vector + function test_perpUploadEncodeHash_1() public { + PerpTypes.FuturesTradeUpload memory t1 = PerpTypes.FuturesTradeUpload({ + tradeId: 417733, + matchId: 1681722208647262950, + accountId: 0x1723cb226c337a417a6022890bc5671ebb4db551db0273536bf1094edf39ed63, + symbolHash: 0x1723cb226c337a417a6022890bc5671ebb4db551db0273536bf1094edf39ed63, + side: true, + tradeQty: 500000000, + notional: 55000000, + executedPrice: 1100000000, + fee: 5000, + feeAssetHash: 0x1723cb226c337a417a6022890bc5671ebb4db551db0273536bf1094edf39ed63, + sumUnitaryFundings: 1000000000000000, + timestamp: 1681693408647 + }); + + PerpTypes.FuturesTradeUpload[] memory trades = new PerpTypes.FuturesTradeUpload[](1); + trades[0] = t1; + + bool succ = Signature.perpUploadEncodeHashVerify( + PerpTypes.FuturesTradeUploadData({ + batchId: 18, + count: 4, + trades: trades, + r: 0x543e72ea14c90ae0422bc5dcc4057b44b1f177780b843651e0d0da504384f4ab, + s: 0x6ad60c31a85437e0cf2ff7c5a0ca9a18a0474d7e3d936cbf0e999dd897dea09d, + v: 0x1b + }), + addr + ); + assertEq(succ, true); + } + + // https://wootraders.atlassian.net/wiki/spaces/ORDER/pages/299009164/Test+vector + function test_perpUploadEncodeHash_2() public { + PerpTypes.FuturesTradeUpload memory t1 = PerpTypes.FuturesTradeUpload({ + tradeId: 417733, + matchId: 1681722208647262950, + accountId: 0x1723cb226c337a417a6022890bc5671ebb4db551db0273536bf1094edf39ed63, + symbolHash: 0x1723cb226c337a417a6022890bc5671ebb4db551db0273536bf1094edf39ed63, + side: true, + tradeQty: 500000000, + notional: 55000000, + executedPrice: 1100000000, + fee: 5000, + feeAssetHash: 0x1723cb226c337a417a6022890bc5671ebb4db551db0273536bf1094edf39ed63, + sumUnitaryFundings: 1000000000000000, + timestamp: 1681693408647 + }); + PerpTypes.FuturesTradeUpload memory t2 = PerpTypes.FuturesTradeUpload({ + tradeId: 417734, + matchId: 1681722208647262951, + accountId: 0x1723cb226c337a417a6022890bc5671ebb4db551db0273536bf1094edf39ed64, + symbolHash: 0x1723cb226c337a417a6022890bc5671ebb4db551db0273536bf1094edf39ed65, + side: false, + tradeQty: 500000001, + notional: 55000001, + executedPrice: 1100000001, + fee: 5001, + feeAssetHash: 0x1723cb226c337a417a6022890bc5671ebb4db551db0273536bf1094edf39ed66, + sumUnitaryFundings: 1000000000000001, + timestamp: 1681693408648 + }); + PerpTypes.FuturesTradeUpload[] memory trades = new PerpTypes.FuturesTradeUpload[](2); + trades[0] = t1; + trades[1] = t2; + + bool succ = Signature.perpUploadEncodeHashVerify( + PerpTypes.FuturesTradeUploadData({ + batchId: 18, + count: 4, + trades: trades, + r: 0xcc91371a8c28fc72544a468691bfbb810487d6c448241a1d4b4889b6c0de2d5b, + s: 0x7a053f078e0f1e791348fa5163dd8d1cc107cea19b47c9e87581cc4af85e0a74, + v: 0x1c + }), + addr + ); + + assertEq(succ, true); + } + + // https://wootraders.atlassian.net/wiki/spaces/ORDER/pages/299009164/Test+vector#%E6%95%B0%E6%8D%AE1.1 + function test_eventUploadEncodeHash_1() public { + EventTypes.WithdrawData memory w1 = EventTypes.WithdrawData({ + tokenAmount: 123, + fee: 5000, + chainId: 10086, + accountId: 0x1723cb226c337a417a6022890bc5671ebb4db551db0273536bf1094edf39ed63, + r: 0x0, + s: 0x0, + v: 0x0, + sender: 0x6a9961Ace9bF0C1B8B98ba11558A4125B1f5EA3f, + withdrawNonce: 9, + receiver: 0x6a9961Ace9bF0C1B8B98ba11558A4125B1f5EA3f, + timestamp: 1683270380530, + brokerId: "woo_dex", + tokenSymbol: "USDC" + }); + + EventTypes.Adl memory a1 = EventTypes.Adl({ + accountId: 0x1723cb226c337a417a6022890bc5671ebb4db551db0273536bf1094edf39ed64, + insuranceAccountId: 0x1723cb226c337a417a6022890bc5671ebb4db551db0273536bf1094edf39ed65, + symbolHash: 0x1723cb226c337a417a6022890bc5671ebb4db551db0273536bf1094edf39ed66, + positionQtyTransfer: 2000000000, + costPositionTransfer: 44000000, + adlPrice: 220000000, + sumUnitaryFundings: 12340000000, + timestamp: 1683270380531 + }); + EventTypes.EventUploadData[] memory events = new EventTypes.EventUploadData[](2); + events[0] = EventTypes.EventUploadData({bizType: 1, eventId: 1, data: abi.encode(w1)}); + events[1] = EventTypes.EventUploadData({bizType: 3, eventId: 3, data: abi.encode(a1)}); + EventTypes.EventUpload memory e1 = EventTypes.EventUpload({ + events: events, + r: 0x0a29a4bd74c2f0d6e20f68ae5361483015b9ff35b650aeb2da3aa9229e19999b, + s: 0x2becba8febb53c1d7c582871a5fb54103b224828f3b8c56dddb0bef57fcb818e, + v: 0x1b, + count: 4, + batchId: 18 + }); + + bool succ = Signature.eventsUploadEncodeHashVerify(e1, addr); + assertEq(succ, true); + } + + // https://wootraders.atlassian.net/wiki/spaces/ORDER/pages/299009164/Test+vector#%E6%95%B0%E6%8D%AE2.1 + function test_eventUploadEncodeHash_2() public { + EventTypes.WithdrawData memory w1 = EventTypes.WithdrawData({ + tokenAmount: 123, + fee: 5000, + chainId: 10086, + accountId: 0x1723cb226c337a417a6022890bc5671ebb4db551db0273536bf1094edf39ed63, + r: 0x0, + s: 0x0, + v: 0x0, + sender: 0x6a9961Ace9bF0C1B8B98ba11558A4125B1f5EA3f, + withdrawNonce: 9, + receiver: 0x6a9961Ace9bF0C1B8B98ba11558A4125B1f5EA3f, + timestamp: 1683270380530, + brokerId: "woo_dex", + tokenSymbol: "USDC" + }); + + EventTypes.Adl memory a1 = EventTypes.Adl({ + accountId: 0x1723cb226c337a417a6022890bc5671ebb4db551db0273536bf1094edf39ed64, + insuranceAccountId: 0x1723cb226c337a417a6022890bc5671ebb4db551db0273536bf1094edf39ed65, + symbolHash: 0x1723cb226c337a417a6022890bc5671ebb4db551db0273536bf1094edf39ed66, + positionQtyTransfer: 2000000000, + costPositionTransfer: 44000000, + adlPrice: 220000000, + sumUnitaryFundings: 12340000000, + timestamp: 1683270380531 + }); + + EventTypes.WithdrawData memory w2 = EventTypes.WithdrawData({ + tokenAmount: 12356, + fee: 5001, + chainId: 10087, + accountId: 0x1723cb226c337a417a6022890bc5671ebb4db551db0273536bf1094edf39ed64, + r: 0x0, + s: 0x0, + v: 0x0, + sender: 0x6a9961Ace9bF0C1B8B98ba11558A4125B1f5EA3f, + withdrawNonce: 10, + receiver: 0x6a9961Ace9bF0C1B8B98ba11558A4125B1f5EA3f, + timestamp: 1683270380531, + brokerId: "woofi_dex", + tokenSymbol: "USDC" + }); + EventTypes.EventUploadData[] memory events = new EventTypes.EventUploadData[](3); + events[0] = EventTypes.EventUploadData({bizType: 1, eventId: 1, data: abi.encode(w1)}); + events[1] = EventTypes.EventUploadData({bizType: 3, eventId: 3, data: abi.encode(a1)}); + events[2] = EventTypes.EventUploadData({bizType: 1, eventId: 4, data: abi.encode(w2)}); + EventTypes.EventUpload memory e1 = EventTypes.EventUpload({ + events: events, + r: 0xfd3ee24f871ae1c8a16aa336b81558f9cc42d2b7891eea8ba1403b1224286419, + s: 0x1aa444dac958a78d7f6a5fe07909118ef2882203a339e37c8bc138f61566449e, + v: 0x1c, + count: 4, + batchId: 18 + }); + + bool succ = Signature.eventsUploadEncodeHashVerify(e1, addr); + assertEq(succ, true); + } + + // https://wootraders.atlassian.net/wiki/spaces/ORDER/pages/299009164/Test+vector#%E6%95%B0%E6%8D%AE3 + function test_eventUploadEncodeHash_3() public { + EventTypes.SettlementExecution[] memory se = new EventTypes.SettlementExecution[](1); + se[0] = EventTypes.SettlementExecution({ + symbolHash: 0x1723cb226c337a417a6022890bc5671ebb4db551db0273536bf1094edf39ed70, + markPrice: 212000000, + sumUnitaryFundings: 1230000000000, + settledAmount: 101000000 + }); + EventTypes.Settlement memory s1 = EventTypes.Settlement({ + accountId: 0x1723cb226c337a417a6022890bc5671ebb4db551db0273536bf1094edf39ed67, + settledAmount: 101000000, + settledAssetHash: 0x1723cb226c337a417a6022890bc5671ebb4db551db0273536bf1094edf39ed68, + insuranceAccountId: 0x1723cb226c337a417a6022890bc5671ebb4db551db0273536bf1094edf39ed69, + insuranceTransferAmount: 55000000, + settlementExecutions: se, + timestamp: 1683270380555 + }); + + EventTypes.EventUploadData[] memory events = new EventTypes.EventUploadData[](1); + events[0] = EventTypes.EventUploadData({bizType: 2, eventId: 7, data: abi.encode(s1)}); + EventTypes.EventUpload memory e1 = EventTypes.EventUpload({ + events: events, + r: 0x63ac7591ee23fbdd865a010cd58c8e4fc76e8b25f1efb9afcd5936366898df38, + s: 0x6d9a0cbe7b8b0dca6fa6d7af1f47b295a05571d0fbbaddff883fa7c70bec15ae, + v: 0x1c, + count: 4, + batchId: 18 + }); + + bool succ = Signature.eventsUploadEncodeHashVerify(e1, addr); + assertEq(succ, true); + } + + // https://wootraders.atlassian.net/wiki/spaces/ORDER/pages/299009164/Test+vector#%E6%95%B0%E6%8D%AE4 + function test_eventUploadEncodeHash_4() public { + EventTypes.WithdrawData memory w1 = EventTypes.WithdrawData({ + tokenAmount: 123, + fee: 5000, + chainId: 10086, + accountId: 0x1723cb226c337a417a6022890bc5671ebb4db551db0273536bf1094edf39ed63, + r: 0x0, + s: 0x0, + v: 0x0, + sender: 0x6a9961Ace9bF0C1B8B98ba11558A4125B1f5EA3f, + withdrawNonce: 9, + receiver: 0x6a9961Ace9bF0C1B8B98ba11558A4125B1f5EA3f, + timestamp: 1683270380530, + brokerId: "woo_dex", + tokenSymbol: "USDC" + }); + + EventTypes.Adl memory a1 = EventTypes.Adl({ + accountId: 0x1723cb226c337a417a6022890bc5671ebb4db551db0273536bf1094edf39ed64, + insuranceAccountId: 0x1723cb226c337a417a6022890bc5671ebb4db551db0273536bf1094edf39ed65, + symbolHash: 0x1723cb226c337a417a6022890bc5671ebb4db551db0273536bf1094edf39ed66, + positionQtyTransfer: 2000000000, + costPositionTransfer: 44000000, + adlPrice: 220000000, + sumUnitaryFundings: 12340000000, + timestamp: 1683270380531 + }); + + EventTypes.WithdrawData memory w2 = EventTypes.WithdrawData({ + tokenAmount: 12356, + fee: 5001, + chainId: 10087, + accountId: 0x1723cb226c337a417a6022890bc5671ebb4db551db0273536bf1094edf39ed64, + r: 0x0, + s: 0x0, + v: 0x0, + sender: 0x6a9961Ace9bF0C1B8B98ba11558A4125B1f5EA3f, + withdrawNonce: 10, + receiver: 0x6a9961Ace9bF0C1B8B98ba11558A4125B1f5EA3f, + timestamp: 1683270380531, + brokerId: "woofi_dex", + tokenSymbol: "USDC" + }); + + EventTypes.SettlementExecution[] memory se = new EventTypes.SettlementExecution[](2); + se[0] = EventTypes.SettlementExecution({ + symbolHash: 0x1723cb226c337a417a6022890bc5671ebb4db551db0273536bf1094edf39ed70, + markPrice: 212000000, + sumUnitaryFundings: 1230000000000, + settledAmount: 101000000 + }); + se[1] = EventTypes.SettlementExecution({ + symbolHash: 0x1723cb226c337a417a6022890bc5671ebb4db551db0273536bf1094edf39ed71, + markPrice: 212000001, + sumUnitaryFundings: 1230000000001, + settledAmount: 101000001 + }); + EventTypes.Settlement memory s1 = EventTypes.Settlement({ + accountId: 0x1723cb226c337a417a6022890bc5671ebb4db551db0273536bf1094edf39ed67, + settledAmount: 101000000, + settledAssetHash: 0x1723cb226c337a417a6022890bc5671ebb4db551db0273536bf1094edf39ed68, + insuranceAccountId: 0x1723cb226c337a417a6022890bc5671ebb4db551db0273536bf1094edf39ed69, + insuranceTransferAmount: 55000000, + settlementExecutions: se, + timestamp: 1683270380555 + }); + + EventTypes.LiquidationTransfer[] memory lt = new EventTypes.LiquidationTransfer[](1); + lt[0] = EventTypes.LiquidationTransfer({ + liquidationTransferId: 2023, + liquidatorAccountId: 0x1723cb226c337a417a6022890bc5671ebb4db551db0273536bf1094edf39ed75, + symbolHash: 0x1723cb226c337a417a6022890bc5671ebb4db551db0273536bf1094edf39ed76, + positionQtyTransfer: 2000000000, + costPositionTransfer: 44000000, + liquidatorFee: 200000, + insuranceFee: 400000, + liquidationFee: 600000, + markPrice: 212000000, + sumUnitaryFundings: 1230000000000 + }); + EventTypes.Liquidation memory l1 = EventTypes.Liquidation({ + liquidatedAccountId: 0x1723cb226c337a417a6022890bc5671ebb4db551db0273536bf1094edf39ed72, + insuranceAccountId: 0x1723cb226c337a417a6022890bc5671ebb4db551db0273536bf1094edf39ed73, + insuranceTransferAmount: 10000001, + liquidatedAssetHash: 0x1723cb226c337a417a6022890bc5671ebb4db551db0273536bf1094edf39ed74, + liquidationTransfers: lt, + timestamp: 1683270380556 + }); + + EventTypes.Settlement memory s2 = EventTypes.Settlement({ + accountId: 0x1723cb226c337a417a6022890bc5671ebb4db551db0273536bf1094edf39ed77, + settledAmount: 101000002, + settledAssetHash: 0x1723cb226c337a417a6022890bc5671ebb4db551db0273536bf1094edf39ed78, + insuranceAccountId: 0x1723cb226c337a417a6022890bc5671ebb4db551db0273536bf1094edf39ed79, + insuranceTransferAmount: 55000002, + settlementExecutions: new EventTypes.SettlementExecution[](0), + timestamp: 1683270380558 + }); + + EventTypes.EventUploadData[] memory events = new EventTypes.EventUploadData[](6); + events[0] = EventTypes.EventUploadData({bizType: 1, eventId: 1, data: abi.encode(w1)}); + events[1] = EventTypes.EventUploadData({bizType: 3, eventId: 3, data: abi.encode(a1)}); + events[2] = EventTypes.EventUploadData({bizType: 1, eventId: 4, data: abi.encode(w2)}); + events[3] = EventTypes.EventUploadData({bizType: 2, eventId: 7, data: abi.encode(s1)}); + events[4] = EventTypes.EventUploadData({bizType: 4, eventId: 9, data: abi.encode(l1)}); + events[5] = EventTypes.EventUploadData({bizType: 2, eventId: 11, data: abi.encode(s2)}); + EventTypes.EventUpload memory e1 = EventTypes.EventUpload({ + events: events, + r: 0xe6efcb099fcc9ebef50514c153f666e3e2c2087c723fa3ec6c767cddaa5ec3f4, + s: 0x7bee154827afce72d3bf6882e028adc319ca20977291da79c331d4474858f0e1, + v: 0x1c, + count: 4, + batchId: 18 + }); + + bool succ = Signature.eventsUploadEncodeHashVerify(e1, addr); + assertEq(succ, true); + } + + // https://wootraders.atlassian.net/wiki/spaces/ORDER/pages/288358489/Operator+Test+cases + function test_eventUploadEncodeHash_extra_1() public { + EventTypes.WithdrawData memory w1 = EventTypes.WithdrawData({ + tokenAmount: 1000000, + fee: 0, + chainId: 43113, + accountId: 0xb336b4dc9f87302da656862ca142a8d454268ae61759bf25d986f863d8374cf1, + r: 0x4a88398c91b3eb572e2f889882bf060764853e71f81b7edb1e7155c39e734b21, + s: 0x03f06b07855e5824bf2bea53d960469d40477a2d5fa007c4d70d8f2426270d0d, + v: 0x1b, + sender: 0xb2EEefB3D6922C4270d174A4020d71D8Bd23C229, + withdrawNonce: 9, + receiver: 0xb2EEefB3D6922C4270d174A4020d71D8Bd23C229, + timestamp: 1689044649193, + brokerId: "woofi_dex", + tokenSymbol: "USDC" + }); + EventTypes.EventUploadData[] memory events = new EventTypes.EventUploadData[](1); + events[0] = EventTypes.EventUploadData({bizType: 1, eventId: 230711030400003, data: abi.encode(w1)}); + EventTypes.EventUpload memory e1 = EventTypes.EventUpload({ + events: events, + r: 0x651929c1b2bfae1904e3a5398fd6ae9f0cd148d51d179ebbe88fab2249522648, + s: 0x38d8776b1a0d21a897fe5a5dab317bb76f90d4fb6429cf3d6387b5725850c0ab, + v: 0x1c, + count: 1, + batchId: 1 + }); + + bool succ = Signature.eventsUploadEncodeHashVerify(e1, addr); + assertEq(succ, true); + } + + // https://wootraders.atlassian.net/wiki/spaces/ORDER/pages/299009164/Test+vector#%E6%95%B0%E6%8D%AE1.2 + function test_marketCfgUploadEncodeHash_1() public { + MarketTypes.PerpPrice[] memory perpPrices = new MarketTypes.PerpPrice[](2); + perpPrices[0] = MarketTypes.PerpPrice({ + indexPrice: 100000777, + markPrice: 100000888, + symbolHash: 0x7e83089239db756ee233fa8972addfea16ae653db0f692e4851aed546b21caeb, + timestamp: 1580794149123 + }); + perpPrices[1] = MarketTypes.PerpPrice({ + indexPrice: 100000123, + markPrice: 100000456, + symbolHash: 0x5a8133e52befca724670dbf2cade550c522c2410dd5b1189df675e99388f509d, + timestamp: 1580794149789 + }); + MarketTypes.UploadPerpPrice memory data = MarketTypes.UploadPerpPrice({ + r: 0x641756217ae53e90d718b1c25222939cf081fc36156c6638bad9758b640f1207, + s: 0x4df04d08e92e39bf0042f775da00456472f6932b02b79f11c80c4f19d2c37f70, + v: 0x1c, + maxTimestamp: 1580794149789, + perpPrices: perpPrices + }); + bool succ = Signature.marketUploadEncodeHashVerify(data, addr); + assertEq(succ, true); + } + + // https://wootraders.atlassian.net/wiki/spaces/ORDER/pages/299009164/Test+vector#%E6%95%B0%E6%8D%AE2.2 + function test_marketCfgUploadEncodeHash_2() public { + MarketTypes.SumUnitaryFunding[] memory sumUnitaryFundings = new MarketTypes.SumUnitaryFunding[](2); + sumUnitaryFundings[0] = MarketTypes.SumUnitaryFunding({ + sumUnitaryFunding: 101200888, + symbolHash: 0x7e83089239db756ee233fa8972addfea16ae653db0f692e4851aed546b21caeb, + timestamp: 1581794149123 + }); + sumUnitaryFundings[1] = MarketTypes.SumUnitaryFunding({ + sumUnitaryFunding: 104400456, + symbolHash: 0x5a8133e52befca724670dbf2cade550c522c2410dd5b1189df675e99388f509d, + timestamp: 1580794149789 + }); + MarketTypes.UploadSumUnitaryFundings memory data = MarketTypes.UploadSumUnitaryFundings({ + r: 0xadacfb14ee22deb3fd8dd8e03fb21279ffd2e7cfc580bde4905af635c96b762a, + s: 0x49d02133737500776481766c5639b7abd2a56bbcbe37329fa5dd37e1f743a908, + v: 0x1b, + maxTimestamp: 1580794149789, + sumUnitaryFundings: sumUnitaryFundings + }); + bool succ = Signature.marketUploadEncodeHashVerify(data, addr); + assertEq(succ, true); + } + + // https://wootraders.atlassian.net/wiki/spaces/ORDER/pages/299009164/Test+vector#settlement + function test_eventUploadEncodeHash_extra_2() public { + EventTypes.SettlementExecution[] memory se = new EventTypes.SettlementExecution[](1); + se[0] = EventTypes.SettlementExecution({ + symbolHash: 0x1723cb226c337a417a6022890bc5671ebb4db551db0273536bf1094edf39ed70, + markPrice: 212000000, + sumUnitaryFundings: 1230000000000, + settledAmount: 101000000 + }); + EventTypes.Settlement memory s1 = EventTypes.Settlement({ + accountId: 0x1723cb226c337a417a6022890bc5671ebb4db551db0273536bf1094edf39ed67, + settledAmount: 101000000, + settledAssetHash: 0x1723cb226c337a417a6022890bc5671ebb4db551db0273536bf1094edf39ed68, + insuranceAccountId: 0x0000000000000000000000000000000000000000000000000000000000000000, + insuranceTransferAmount: 0, + settlementExecutions: se, + timestamp: 1683270380555 + }); + + EventTypes.EventUploadData[] memory events = new EventTypes.EventUploadData[](1); + events[0] = EventTypes.EventUploadData({bizType: 2, eventId: 7, data: abi.encode(s1)}); + EventTypes.EventUpload memory e1 = EventTypes.EventUpload({ + events: events, + r: 0xd6248d44b4f0750cb9c46b615c1c58959d815288ecd2c7a0c43bf02f5f9ef1d0, + s: 0x1d68800fb0743f1355f32f6eefb86fc599af4c949ab45c585913de37af4658ac, + v: 0x1c, + count: 4, + batchId: 18 + }); + + bool succ = Signature.eventsUploadEncodeHashVerify(e1, addr); + assertEq(succ, true); + } + + // https://wootraders.atlassian.net/wiki/spaces/ORDER/pages/445056152/FeeDisutrubution+Event+Upload + function test_eventUploadEncodeHash_feeDistribution() public { + EventTypes.FeeDistribution memory fee0 = EventTypes.FeeDistribution({ + fromAccountId: 0x9ff99a5d6cb71a3ef897b0fff5f5801af6dc5f72d8f1608e61409b8fc965bd68, + toAccountId: 0xc69f41c55c00e4d875b3e82eeb0fcda3de2090a10130baf3c1ffee0f2e7ce243, + amount: 1231245125, + tokenHash: 0xd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa + }); + EventTypes.FeeDistribution memory fee1 = EventTypes.FeeDistribution({ + fromAccountId: 0xc69f41c55c00e4d875b3e82eeb0fcda3de2090a10130baf3c1ffee0f2e7ce243, + toAccountId: 0x9ff99a5d6cb71a3ef897b0fff5f5801af6dc5f72d8f1608e61409b8fc965bd68, + amount: 6435342234, + tokenHash: 0xd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa + }); + + EventTypes.EventUploadData[] memory events = new EventTypes.EventUploadData[](2); + events[0] = EventTypes.EventUploadData({bizType: 5, eventId: 1274, data: abi.encode(fee0)}); + events[1] = EventTypes.EventUploadData({bizType: 5, eventId: 1277, data: abi.encode(fee1)}); + EventTypes.EventUpload memory e1 = EventTypes.EventUpload({ + events: events, + r: 0x26eb59ba41e0a9e1c729c8d9f7e766ee4213886e13dfa6d985151180ff3af41f, + s: 0x798c57e7dbf574c52a5583299c460ba70ef19482bec4c8fa2edbdaf01ab2fa95, + v: 0x1c, + count: 2, + batchId: 7888 + }); + + bool succ = Signature.eventsUploadEncodeHashVerify(e1, addr); + assertEq(succ, true); + } + + // https://wootraders.atlassian.net/wiki/spaces/ORDER/pages/459277365/DelegateSigner+event+upload + function test_eventUploadEncodeHash_delegateSigner() public { + EventTypes.DelegateSigner memory delegateSigner0 = EventTypes.DelegateSigner({ + delegateSigner: 0xa3255bb283A607803791ba8A202262f4AB28b0B2, + delegateContract: 0xa757D29D25116a657F2929DE61BCcA6173f731fE, + brokerHash: 0x1723cb226c337a417a6022890bc5671ebb4db551db0273536bf1094edf39ed66, + chainId: 42161 + }); + + EventTypes.DelegateSigner memory delegateSigner1 = EventTypes.DelegateSigner({ + delegateSigner: 0xa3255bb283A607803791ba8A202262f4AB28b0B2, + delegateContract: 0xa757D29D25116a657F2929DE61BCcA6173f731fE, + brokerHash: 0x1723cb226c337a417a6022890bc5671ebb4db551db0273536bf1094edf39ed66, + chainId: 42162 + }); + + EventTypes.EventUploadData[] memory events = new EventTypes.EventUploadData[](2); + events[0] = EventTypes.EventUploadData({bizType: 6, eventId: 234, data: abi.encode(delegateSigner0)}); + events[1] = EventTypes.EventUploadData({bizType: 6, eventId: 235, data: abi.encode(delegateSigner1)}); + EventTypes.EventUpload memory e1 = EventTypes.EventUpload({ + events: events, + r: 0x485d4bda8c7ea56f553e486cbf311ab0575a257fa431b72c141a208fbed4eaca, + s: 0x683916616409b086f102e1b58c08bc324e3bf17ebdefc6389813f67f934f5554, + v: 0x1b, + count: 2, + batchId: 7888 + }); + + bool succ = Signature.eventsUploadEncodeHashVerify(e1, addr); + assertEq(succ, true); + } + + // based on real data uploaded to OperatorManager contract + function test_eventUploadEncodeHash_delegateSigner1() public { + EventTypes.DelegateSigner memory delegateSigner0 = EventTypes.DelegateSigner({ + delegateSigner: 0xDd3287043493E0a08d2B348397554096728B459c, + delegateContract: 0x65E6b31cC38aC83E0f11ACc67eaE5f7EFd31aB18, + brokerHash: 0x6ca2f644ef7bd6d75953318c7f2580014941e753b3c6d54da56b3bf75dd14dfc, + chainId: 11155420 + }); + + EventTypes.DelegateSigner memory delegateSigner1 = EventTypes.DelegateSigner({ + delegateSigner: 0xDd3287043493E0a08d2B348397554096728B459c, + delegateContract: 0x31c30d825a8A98C67C1c92b86e652f877435970b, + brokerHash: 0x6ca2f644ef7bd6d75953318c7f2580014941e753b3c6d54da56b3bf75dd14dfc, + chainId: 421614 + }); + + EventTypes.DelegateSigner memory delegateSigner2 = EventTypes.DelegateSigner({ + delegateSigner: 0x2bAC7A6771613440989432c9B3B9a45dDd15e657, + delegateContract: 0xa4394b62261061C629800C6D86D153A9F38f0cbB, + brokerHash: 0x6ca2f644ef7bd6d75953318c7f2580014941e753b3c6d54da56b3bf75dd14dfc, + chainId: 421614 + }); + + EventTypes.DelegateSigner memory delegateSigner3 = EventTypes.DelegateSigner({ + delegateSigner: 0x2bAC7A6771613440989432c9B3B9a45dDd15e657, + delegateContract: 0xa4394b62261061C629800C6D86D153A9F38f0cbB, + brokerHash: 0x083098c593f395bea1de45dda552d9f14e8fcb0be3faaa7a1903c5477d7ba7fd, + chainId: 421614 + }); + + EventTypes.EventUploadData[] memory events = new EventTypes.EventUploadData[](4); + events[0] = EventTypes.EventUploadData({bizType: 6, eventId: 1439, data: abi.encode(delegateSigner0)}); + events[1] = EventTypes.EventUploadData({bizType: 6, eventId: 1440, data: abi.encode(delegateSigner1)}); + events[2] = EventTypes.EventUploadData({bizType: 6, eventId: 1441, data: abi.encode(delegateSigner2)}); + events[3] = EventTypes.EventUploadData({bizType: 6, eventId: 1442, data: abi.encode(delegateSigner3)}); + EventTypes.EventUpload memory e1 = EventTypes.EventUpload({ + events: events, + r: 0x12e4dfd5d7b7730b23a20461ac0585d8bae27b3efdd5bcaef0db1c7fe314f344, + s: 0x29e11bebc46a5ae183616f88a8b8278bb83f5927a66dd0bb390ab8ec46be2a54, + v: 0x1b, + count: 4, + batchId: 882 + }); + bool succ = Signature.eventsUploadEncodeHashVerify(e1, 0xDdDd1555A17d3Dad86748B883d2C1ce633A7cd88); + assertEq(succ, true); + } + + // https://wootraders.atlassian.net/wiki/spaces/ORDER/pages/459277365/DelegateSigner+event+upload + function test_eventUploadEncodeHash_delegateWitdraw() public { + EventTypes.WithdrawData memory withdraw0 = EventTypes.WithdrawData({ + tokenAmount: 123, + fee: 5000, + chainId: 10086, + accountId: 0x1723cb226c337a417a6022890bc5671ebb4db551db0273536bf1094edf39ed63, + r: 0x0, + s: 0x0, + v: 0x0, + sender: 0x6a9961Ace9bF0C1B8B98ba11558A4125B1f5EA3f, + withdrawNonce: 9, + receiver: 0x6a9961Ace9bF0C1B8B98ba11558A4125B1f5EA3f, + timestamp: 1683270380530, + brokerId: "woo_dex", + tokenSymbol: "USDC" + }); + + EventTypes.WithdrawData memory delegateWithdraw0 = EventTypes.WithdrawData({ + tokenAmount: 12356, + fee: 5001, + chainId: 10087, + accountId: 0x1723cb226c337a417a6022890bc5671ebb4db551db0273536bf1094edf39ed64, + r: 0x0, + s: 0x0, + v: 0x0, + sender: 0x6a9961Ace9bF0C1B8B98ba11558A4125B1f5EA3f, + withdrawNonce: 10, + receiver: 0x6a9961Ace9bF0C1B8B98ba11558A4125B1f5EA3f, + timestamp: 1683270380531, + brokerId: "woofi_dex", + tokenSymbol: "USDC" + }); + + EventTypes.DelegateSigner memory delegateSigner0 = EventTypes.DelegateSigner({ + delegateSigner: 0xa3255bb283A607803791ba8A202262f4AB28b0B2, + delegateContract: 0xa757D29D25116a657F2929DE61BCcA6173f731fE, + brokerHash: 0x1723cb226c337a417a6022890bc5671ebb4db551db0273536bf1094edf39ed66, + chainId: 42162 + }); + + EventTypes.FeeDistribution memory fee0 = EventTypes.FeeDistribution({ + fromAccountId: 0xc69f41c55c00e4d875b3e82eeb0fcda3de2090a10130baf3c1ffee0f2e7ce243, + toAccountId: 0x9ff99a5d6cb71a3ef897b0fff5f5801af6dc5f72d8f1608e61409b8fc965bd68, + amount: 6435342234, + tokenHash: 0xd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa + }); + + EventTypes.EventUploadData[] memory events = new EventTypes.EventUploadData[](4); + + events[0] = EventTypes.EventUploadData({bizType: 1, eventId: 1, data: abi.encode(withdraw0)}); + events[1] = EventTypes.EventUploadData({bizType: 7, eventId: 4, data: abi.encode(delegateWithdraw0)}); + events[2] = EventTypes.EventUploadData({bizType: 6, eventId: 235, data: abi.encode(delegateSigner0)}); + events[3] = EventTypes.EventUploadData({bizType: 5, eventId: 1277, data: abi.encode(fee0)}); + + EventTypes.EventUpload memory e1 = EventTypes.EventUpload({ + events: events, + r: 0x1843d7a15a61c3f6d9b23f322af959ec7c399d4db2acb6d38880abe37e256688, + s: 0x7aee366da8bf51c9a2f5312f64c660fc34c88db19d72203624a1ea27d1c75ac6, + v: 0x1b, + count: 4, + batchId: 7888 + }); + bool succ = Signature.eventsUploadEncodeHashVerify(e1, addr); + assertEq(succ, true); + } + + // https://wootraders.atlassian.net/wiki/spaces/ORDER/pages/401440769/Rebalance+Test+vector#Burn + function test_rebalanceBurnUploadEncodeHash() public { + RebalanceTypes.RebalanceBurnUploadData memory data = RebalanceTypes.RebalanceBurnUploadData({ + r: 0x80e4cf10349a922a52efb8764cd07a107dc9a68865fd2a5e4ee539199b60f217, + s: 0x44035df25557de70ebbf18d600052995a925096ea7e6bd217262e965f33e5565, + v: 0x1c, + rebalanceId: 123, + amount: 1234567, + tokenHash: 0xd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa, + burnChainId: 43113, + mintChainId: 421613 + }); + bool succ = Signature.rebalanceBurnUploadEncodeHashVerify(data, addr); + assertEq(succ, true); + } + + // https://wootraders.atlassian.net/wiki/spaces/ORDER/pages/401440769/Rebalance+Test+vector#Mint + function test_rebalanceMintUploadEncodeHash() public { + RebalanceTypes.RebalanceMintUploadData memory data = RebalanceTypes.RebalanceMintUploadData({ + r: 0xc9dc61f67d71ffcfebacf463026957c466e452c0d1e292bfde8eadf221f3e78b, + s: 0x07363a680273ecf7030c8a869d23c82b5564463bb37b9340921c8b4bdc03924f, + v: 0x1b, + rebalanceId: 123, + amount: 1234567, + tokenHash: 0xd6aca1be9729c13d677335161321649cccae6a591554772516700f986f942eaa, + burnChainId: 43113, + mintChainId: 421613, + messageBytes: abi.encodePacked( + hex"000000000000000300000000000000000000033800000000000000000000000012dcfd3fe2e9eac2859fd1ed86d2ab8c5a2f9352000000000000000000000000d0c3da58f55358142b8d3e06c1c30c5c6114efe8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fd064a18f3bf249cf1f87fc203e90d8f650f2d63000000000000000000000000dd3287043493e0a08d2b348397554096728b459c00000000000000000000000000000000000000000000000000000000004c4b40000000000000000000000000dd3287043493e0a08d2b348397554096728b459c" + ), + messageSignature: abi.encodePacked( + hex"b8ccbb12d7cda9ca09dabf2440b18e731475ec613689fb3ac4469d09eeef18fe0bf53b8818780a643dc9e191de321504139a748df7ea037b51094fa0a6dadda91ba8b856e7d1af15c56af225a3bc442c6f46f48ac17d46a30711027d3019f4a40e3d55a507fdf11a4265031940ff54f6971139de1622827c5fee33e4ee82d7f07d1b" + ) + }); + bool succ = Signature.rebalanceMintUploadEncodeHashVerify(data, addr); + assertEq(succ, true); + } +} diff --git a/test/functionTest/Utils.t.sol b/test/functionTest/Utils.t.sol new file mode 100644 index 0000000..2e2cc13 --- /dev/null +++ b/test/functionTest/Utils.t.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.19; + +import "forge-std/Test.sol"; +import "../../src/library/Utils.sol"; + +contract UtilsTest is Test { + // https://wootraders.atlassian.net/wiki/spaces/ORDER/pages/304513342/Account+ID+Calculation + function test_calculateAccountId() public { + assertEq( + Utils.getAccountId(0x5B38Da6a701c568545dCfcB03FcB875f56beddC4, "ref_dex"), + 0x29783c8cfabffb495d176dda502c197c1ec61258c865c3a01c9573ad4934cf81 + ); + } + + function test_calculateStringHash() public { + assertEq( + Utils.calculateStringHash("ref_dex"), 0x7992f050dbd5f109a9eb7010a8f3a688214c047dd7f69b96a9774f556ccfa174 + ); + } + + function test_validateAccountId() public { + assertTrue( + Utils.validateAccountId( + 0x29783c8cfabffb495d176dda502c197c1ec61258c865c3a01c9573ad4934cf81, + 0x7992f050dbd5f109a9eb7010a8f3a688214c047dd7f69b96a9774f556ccfa174, + 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4 + ) + ); + } + + function test_toBytes32() public { + assertEq( + Utils.toBytes32(0x5B38Da6a701c568545dCfcB03FcB875f56beddC4), + hex"0000000000000000000000005B38Da6a701c568545dCfcB03FcB875f56beddC4" + ); + } +} diff --git a/test/mock/LedgerCrossChainManagerMock.sol b/test/mock/LedgerCrossChainManagerMock.sol new file mode 100644 index 0000000..1779cc5 --- /dev/null +++ b/test/mock/LedgerCrossChainManagerMock.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "../../src/interface/ILedgerCrossChainManager.sol"; +import "openzeppelin-contracts/contracts/access/Ownable.sol"; +import "../../src/interface/ILedger.sol"; + +contract LedgerCrossChainManagerMock is ILedgerCrossChainManager, Ownable { + ILedger public ledger; + + function withdraw(EventTypes.WithdrawData calldata data) external override {} + + function setLedger(address _ledger) external override { + ledger = ILedger(_ledger); + } + + function setOperatorManager(address _operatorManager) external override {} + + function setCrossChainRelay(address _crossChainRelay) external override {} + + function withdrawFinishMock(AccountTypes.AccountWithdraw memory message) external { + ledger.accountWithDrawFinish(message); + } + + function burn(RebalanceTypes.RebalanceBurnCCData memory data) external override {} + function mint(RebalanceTypes.RebalanceMintCCData memory data) external override {} +} diff --git a/test/mock/VaultCrossChainManagerMock.sol b/test/mock/VaultCrossChainManagerMock.sol new file mode 100644 index 0000000..a4d33ac --- /dev/null +++ b/test/mock/VaultCrossChainManagerMock.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "../../src/interface/IVaultCrossChainManager.sol"; +import "openzeppelin-contracts/contracts/access/Ownable.sol"; + +contract VaultCrossChainManagerMock is IVaultCrossChainManager, Ownable { + function getDepositFee(VaultTypes.VaultDeposit memory data) external view override returns (uint256) {} + + function deposit(VaultTypes.VaultDeposit memory data) external override {} + + function burnFinish(RebalanceTypes.RebalanceBurnCCFinishData memory data) external override {} + + function mintFinish(RebalanceTypes.RebalanceMintCCFinishData memory data) external override {} + + function withdraw(VaultTypes.VaultWithdraw memory data) external override {} + + function setVault(address _vault) external override {} + + function setCrossChainRelay(address _crossChainRelay) external override {} + + function depositWithFee(VaultTypes.VaultDeposit memory _data) external payable override {} +} diff --git a/test/mock/VaultManagerBuggy.sol b/test/mock/VaultManagerBuggy.sol new file mode 100644 index 0000000..3b10841 --- /dev/null +++ b/test/mock/VaultManagerBuggy.sol @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.18; + +import "../../src/interface/IVaultManager.sol"; +import "../../src/LedgerComponent.sol"; + +contract VaultManagerBuggy is IVaultManager, LedgerComponent { + // crossChainManagerAddress contract address + address public crossChainManagerAddress; + // valut balance, used for check if withdraw is valid + mapping(bytes32 => mapping(uint256 => uint128)) private tokenBalanceOnchain; + mapping(bytes32 => mapping(uint256 => bool)) private allowedToken; // supported token on each chain + mapping(bytes32 => bool) private allowedBroker; // supported broker + + constructor() { + _disableInitializers(); + } + + function initialize() external override(IVaultManager, LedgerComponent) initializer { + __Ownable_init(); + } + + function getBalance(bytes32 _tokenHash, uint256 _chainId) external view override returns (uint128) {} + + function addBalance(bytes32 _tokenHash, uint256 _chainId, uint128 _deltaBalance) external override { + // should be add but sub + tokenBalanceOnchain[_tokenHash][_chainId] -= _deltaBalance; + } + + function subBalance(bytes32 _tokenHash, uint256 _chainId, uint128 _deltaBalance) external override { + // should be sub but add + tokenBalanceOnchain[_tokenHash][_chainId] += _deltaBalance; + } + + function setAllowedBroker(bytes32 _brokerHash, bool _allowed) external override {} + + function getAllowedBroker(bytes32 _brokerHash) external view override returns (bool) {} + + function setAllowedChainToken(bytes32 _tokenHash, uint256 _chainId, bool _allowed) external override {} + + function getAllowedChainToken(bytes32 _tokenHash, uint256 _chainId) external view override returns (bool) {} + + function setAllowedSymbol(bytes32 _symbolHash, bool _allowed) external override {} + + function getAllowedSymbol(bytes32 _symbolHash) external view override returns (bool) {} + + function getAllAllowedToken() external view override returns (bytes32[] memory) {} + + function getAllAllowedBroker() external view override returns (bytes32[] memory) {} + + function getAllAllowedSymbol() external view override returns (bytes32[] memory) {} + + function setAllowedToken(bytes32 _tokenHash, bool _allowed) external override {} + + function getAllowedToken(bytes32 _tokenHash) external view override returns (bool) {} + + function getFrozenBalance(bytes32 _tokenHash, uint256 _chainId) external view override returns (uint128) {} + + function frozenBalance(bytes32 _tokenHash, uint256 _chainId, uint128 _deltaBalance) external override {} + + function finishFrozenBalance(bytes32 _tokenHash, uint256 _chainId, uint128 _deltaBalance) external override {} + + function setMaxWithdrawFee(bytes32 _tokenHash, uint128 _maxWithdrawFee) external override {} + + function getMaxWithdrawFee(bytes32 _tokenHash) external view override returns (uint128) {} + + function setChain2cctpMeta(uint256 chainId, uint32 cctpDomain, address vaultAddress) external override {} + + function executeRebalanceBurn(RebalanceTypes.RebalanceBurnUploadData calldata data) + external + override + returns (uint32, address) + {} + + function rebalanceBurnFinish(RebalanceTypes.RebalanceBurnCCFinishData calldata data) external override {} + + function executeRebalanceMint(RebalanceTypes.RebalanceMintUploadData calldata data) external override {} + + function rebalanceMintFinish(RebalanceTypes.RebalanceMintCCFinishData calldata data) external override {} + + function getRebalanceStatus(uint64 rebalanceId) + external + view + override + returns (RebalanceTypes.RebalanceStatus memory) + {} +} diff --git a/test/typesHelper/AccountTypePositionHelper.t.sol b/test/typesHelper/AccountTypePositionHelper.t.sol new file mode 100644 index 0000000..f8249ab --- /dev/null +++ b/test/typesHelper/AccountTypePositionHelper.t.sol @@ -0,0 +1,350 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.19; + +import "forge-std/Test.sol"; +import "../../src/library/typesHelper/AccountTypePositionHelper.sol"; + +contract AccountTypePositionHelperTest is Test { + using AccountTypePositionHelper for AccountTypes.PerpPosition; + + AccountTypes.PerpPosition position; + + function test_halfUp16_8() public { + { + int128 a = AccountTypePositionHelper.halfUp16_8(10, 11); + assertEq(a, 1); + int128 b = AccountTypePositionHelper.halfUp16_8(12, 11); + assertEq(b, 1); + int128 c = AccountTypePositionHelper.halfUp16_8(17, 11); + assertEq(c, 2); + int128 d = AccountTypePositionHelper.halfUp16_8(21, 11); + assertEq(d, 2); + int128 e = AccountTypePositionHelper.halfUp16_8(22, 11); + assertEq(e, 2); + } + { + int128 a = AccountTypePositionHelper.halfUp16_8(4, 10); + assertEq(a, 0); + int128 b = AccountTypePositionHelper.halfUp16_8(5, 10); + assertEq(b, 1); + int128 c = AccountTypePositionHelper.halfUp16_8(6, 10); + assertEq(c, 1); + int128 d = AccountTypePositionHelper.halfUp16_8(14, 10); + assertEq(d, 1); + int128 e = AccountTypePositionHelper.halfUp16_8(15, 10); + assertEq(e, 2); + int128 f = AccountTypePositionHelper.halfUp16_8(16, 10); + assertEq(f, 2); + } + { + int128 a = AccountTypePositionHelper.halfUp16_8(-10, 11); + assertEq(a, -1); + int128 b = AccountTypePositionHelper.halfUp16_8(-12, 11); + assertEq(b, -1); + int128 c = AccountTypePositionHelper.halfUp16_8(-17, 11); + assertEq(c, -2); + int128 d = AccountTypePositionHelper.halfUp16_8(-21, 11); + assertEq(d, -2); + int128 e = AccountTypePositionHelper.halfUp16_8(-22, 11); + assertEq(e, -2); + } + { + int128 a = AccountTypePositionHelper.halfUp16_8(-4, 10); + assertEq(a, 0); + int128 b = AccountTypePositionHelper.halfUp16_8(-5, 10); + assertEq(b, -1); + int128 c = AccountTypePositionHelper.halfUp16_8(-6, 10); + assertEq(c, -1); + int128 d = AccountTypePositionHelper.halfUp16_8(-14, 10); + assertEq(d, -1); + int128 e = AccountTypePositionHelper.halfUp16_8(-15, 10); + assertEq(e, -2); + int128 f = AccountTypePositionHelper.halfUp16_8(-16, 10); + assertEq(f, -2); + } + } + + function test_halfDown16_8() public { + { + int128 a = AccountTypePositionHelper.halfDown16_8(10, 11); + assertEq(a, 1); + int128 b = AccountTypePositionHelper.halfDown16_8(12, 11); + assertEq(b, 1); + int128 c = AccountTypePositionHelper.halfDown16_8(17, 11); + assertEq(c, 2); + int128 d = AccountTypePositionHelper.halfDown16_8(21, 11); + assertEq(d, 2); + int128 e = AccountTypePositionHelper.halfDown16_8(22, 11); + assertEq(e, 2); + } + { + int128 a = AccountTypePositionHelper.halfDown16_8(4, 10); + assertEq(a, 0); + int128 b = AccountTypePositionHelper.halfDown16_8(5, 10); + assertEq(b, 0); + int128 c = AccountTypePositionHelper.halfDown16_8(6, 10); + assertEq(c, 1); + int128 d = AccountTypePositionHelper.halfDown16_8(14, 10); + assertEq(d, 1); + int128 e = AccountTypePositionHelper.halfDown16_8(15, 10); + assertEq(e, 1); + int128 f = AccountTypePositionHelper.halfDown16_8(16, 10); + assertEq(f, 2); + } + { + int128 a = AccountTypePositionHelper.halfDown16_8(-10, 11); + assertEq(a, -1); + int128 b = AccountTypePositionHelper.halfDown16_8(-12, 11); + assertEq(b, -1); + int128 c = AccountTypePositionHelper.halfDown16_8(-17, 11); + assertEq(c, -2); + int128 d = AccountTypePositionHelper.halfDown16_8(-21, 11); + assertEq(d, -2); + int128 e = AccountTypePositionHelper.halfDown16_8(-22, 11); + assertEq(e, -2); + } + { + int128 a = AccountTypePositionHelper.halfDown16_8(-4, 10); + assertEq(a, 0); + int128 b = AccountTypePositionHelper.halfDown16_8(-5, 10); + assertEq(b, 0); + int128 c = AccountTypePositionHelper.halfDown16_8(-6, 10); + assertEq(c, -1); + int128 d = AccountTypePositionHelper.halfDown16_8(-14, 10); + assertEq(d, -1); + int128 e = AccountTypePositionHelper.halfDown16_8(-15, 10); + assertEq(e, -1); + int128 f = AccountTypePositionHelper.halfDown16_8(-16, 10); + assertEq(f, -2); + } + } + + function test_charge_funding_fee() public { + position = AccountTypes.PerpPosition({ + positionQty: 1e6, + costPosition: 0, + lastSumUnitaryFundings: 1e15, + lastExecutedPrice: 0, + lastSettledPrice: 0, + averageEntryPrice: 0, + openingCost: 0, + lastAdlPrice: 0 + }); + int128 lastSumUnitaryFundings = 1.1e15; + position.chargeFundingFee(lastSumUnitaryFundings); + assertEq(position.costPosition, 1e3); + + position.lastSumUnitaryFundings = 1e15; + position.costPosition = 1e3; + lastSumUnitaryFundings = 0.8e15; + position.chargeFundingFee(lastSumUnitaryFundings); + assertEq(position.costPosition, -1e3); + + position.lastSumUnitaryFundings = 1e15; + position.costPosition = 0; + lastSumUnitaryFundings = 1e15 + 1; + position.chargeFundingFee(lastSumUnitaryFundings); + assertEq(position.costPosition, 1); + + position.lastSumUnitaryFundings = 1e15; + position.costPosition = 1e3; + lastSumUnitaryFundings = 0.9e15 + 1; + position.chargeFundingFee(lastSumUnitaryFundings); + assertEq(position.costPosition, 1); + + position.lastSumUnitaryFundings = 1e15; + position.costPosition = 0; + position.positionQty = 1e8; + lastSumUnitaryFundings = 1e15 - 1; + position.chargeFundingFee(lastSumUnitaryFundings); + assertEq(position.costPosition, 0); + + position.lastSumUnitaryFundings = 2e15 + 2; + position.costPosition = 1e3; + position.positionQty = -4e8; + lastSumUnitaryFundings = 3e15 + 3; + position.chargeFundingFee(lastSumUnitaryFundings); + assertEq(position.costPosition, -3999000); + + position.lastSumUnitaryFundings = 2e15 + 2; + position.costPosition = 1e3; + position.positionQty = 4e8; + lastSumUnitaryFundings = 3e15 + 3; + position.chargeFundingFee(lastSumUnitaryFundings); + assertEq(position.costPosition, 4001001); + } + + function test_cal_average_entry_price() public { + position = AccountTypes.PerpPosition({ + positionQty: 0, + costPosition: 0, + lastSumUnitaryFundings: 0, + lastExecutedPrice: 0, + lastSettledPrice: 0, + averageEntryPrice: 0, + openingCost: 0, + lastAdlPrice: 0 + }); + + position.calAverageEntryPrice(1e8, 1e11, 0); + assertEq(position.openingCost, -1e11); + assertEq(position.averageEntryPrice, 1e11); + + position.positionQty = 1e8; + position.calAverageEntryPrice(1e8, 2e11, 0); + assertEq(position.openingCost, -3e11); + assertEq(position.averageEntryPrice, 1.5e11); + + position.positionQty = 2e8; + position.calAverageEntryPrice(-1e8, 3e11, 0); + assertEq(position.openingCost, -1.5e11); + assertEq(position.averageEntryPrice, 1.5e11); + + position.positionQty = 1e8; + position.calAverageEntryPrice(-2e8, 3e11, 0); + assertEq(position.openingCost, 3e11); + assertEq(position.averageEntryPrice, 3e11); + } + + // https://wootraders.atlassian.net/wiki/spaces/ORDER/pages/250052731/CeFi+avg+open+price+calculation#Corner-cases + function test_average_entry_price_corner_case() public { + position = AccountTypes.PerpPosition({ + positionQty: 0, + costPosition: 0, + lastSumUnitaryFundings: 0, + lastExecutedPrice: 0, + lastSettledPrice: 0, + averageEntryPrice: 0, + openingCost: 0, + lastAdlPrice: 0 + }); + + // buy 1.11111 @ 2.22222 + position.calAverageEntryPrice(111_111_000, 222_222_000, 0); + assertEq(position.openingCost, -246_913_086); + assertEq(position.averageEntryPrice, 222_222_000); + + // sell 0.12345 @ 2.66666 + position.positionQty = 111_111_000; + position.calAverageEntryPrice(-12_345_000, 266_666_000, 0); + assertEq(position.openingCost, -219_479_780); + assertEq(position.averageEntryPrice, 222_221_999); + + // sell 1.56789 @ 2.88888 + position.positionQty = 98_766_000; + position.calAverageEntryPrice(-156_789_000, 288_888_000, 0); + assertEq(position.openingCost, 167_621_484); + assertEq(position.averageEntryPrice, 288_888_000); + + // sell 1.98765 @ 2.22222 + position.positionQty = -58_023_000; + position.calAverageEntryPrice(-198_765_000, 222_222_000, 0); + assertEq(position.openingCost, 609_321_042); + assertEq(position.averageEntryPrice, 237_285_637); + } + + function test_half_down_up_boundary() public { + // half_down16_8 + int128 openingCost = -50_000_000_000_000_000; + int128 holding = 200_000_000; + int128 perpPosition = AccountTypePositionHelper.halfDown16_8(openingCost, holding); + assertEq(perpPosition, -250_000_000); + + openingCost = -50_000_000_100_000_000; + holding = 200_000_000; + perpPosition = AccountTypePositionHelper.halfDown16_8(openingCost, holding); + assertEq(perpPosition, -250_000_000); + + openingCost = -50_000_000_100_000_001; + holding = 200_000_000; + perpPosition = AccountTypePositionHelper.halfDown16_8(openingCost, holding); + assertEq(perpPosition, -250_000_001); + + openingCost = 50_000_000_000_000_000; + holding = 200_000_000; + perpPosition = AccountTypePositionHelper.halfDown16_8(openingCost, holding); + assertEq(perpPosition, 250_000_000); + + openingCost = 50_000_000_100_000_000; + holding = 200_000_000; + perpPosition = AccountTypePositionHelper.halfDown16_8(openingCost, holding); + assertEq(perpPosition, 250_000_000); + + openingCost = 50_000_000_100_000_001; + holding = 200_000_000; + perpPosition = AccountTypePositionHelper.halfDown16_8(openingCost, holding); + assertEq(perpPosition, 250_000_001); + + // half_up24_8 + openingCost = -5_000_000_000_000_000_000_000_000; + holding = 200_000_000; + perpPosition = AccountTypePositionHelper.halfUp24_8(openingCost, holding); + assertEq(perpPosition, -25_000_000_000_000_000); + + openingCost = -5_000_000_009_999_999_900_000_000; + holding = 200_000_000; + perpPosition = AccountTypePositionHelper.halfUp24_8(openingCost, holding); + assertEq(perpPosition, -25_000_000_000_000_000); + + openingCost = -5_000_000_010_000_000_000_000_000; + holding = 200_000_000; + perpPosition = AccountTypePositionHelper.halfUp24_8(openingCost, holding); + assertEq(perpPosition, -25_000_000_100_000_000); + + openingCost = 5_000_000_000_000_000_000_000_000; + holding = 200_000_000; + perpPosition = AccountTypePositionHelper.halfUp24_8(openingCost, holding); + assertEq(perpPosition, 25_000_000_000_000_000); + + openingCost = 5_000_000_009_999_999_900_000_000; + holding = 200_000_000; + perpPosition = AccountTypePositionHelper.halfUp24_8(openingCost, holding); + assertEq(perpPosition, 25_000_000_000_000_000); + + openingCost = 5_000_000_010_000_000_000_000_000; + holding = 200_000_000; + perpPosition = AccountTypePositionHelper.halfUp24_8(openingCost, holding); + assertEq(perpPosition, 25_000_000_100_000_000); + } + + function test_huge_holding() public { + position = AccountTypes.PerpPosition({ + positionQty: 1_000_000_000_000_000, + costPosition: 0, + lastSumUnitaryFundings: 0, + lastExecutedPrice: 0, + lastSettledPrice: 0, + averageEntryPrice: 200_000_000, + openingCost: 0, + lastAdlPrice: 0 + }); + + // sell 20000000.11111 @ 2.22222 + position.calAverageEntryPrice(-2_000_000_011_111_000, 222_222_000, 0); + assertEq(position.openingCost, 2222220024691086); + assertEq(position.averageEntryPrice, 222222000); + + position = AccountTypes.PerpPosition({ + positionQty: 0, + costPosition: 0, + lastSumUnitaryFundings: 0, + lastExecutedPrice: 0, + lastSettledPrice: 0, + averageEntryPrice: 0, + openingCost: 0, + lastAdlPrice: 0 + }); + + // 100 billion*10^8 + int128 qtyDiff = 10_000_000_000_000_000_000; + int128 price = 222_222_000; + // sell 20000000.11111 @ 2.22222 + position.calAverageEntryPrice(qtyDiff, price, 0); + assertEq(position.openingCost, -22222200000000000000); + assertEq(position.averageEntryPrice, 222222000); + position.positionQty += qtyDiff; + position.calAverageEntryPrice(-2 * qtyDiff, price, 0); + assertEq(position.openingCost, 22222200000000000000); + assertEq(position.averageEntryPrice, 222222000); + } +} diff --git a/test/typesHelper/SafeCastHelper.t.sol b/test/typesHelper/SafeCastHelper.t.sol new file mode 100644 index 0000000..f00b6e0 --- /dev/null +++ b/test/typesHelper/SafeCastHelper.t.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.19; + +import "forge-std/Test.sol"; +import "../../src/library/typesHelper/SafeCastHelper.sol"; + +contract SafeCastHelperTest is Test { + using SafeCastHelper for *; + + function test_cast_positive() public { + uint128 y = 123; + int128 z = y.toInt128(); + assertEq(z, 123); + uint128 x = z.toUint128(); + assertEq(x, 123); + } + + function test_cast_0() public { + uint128 y = 0; + int128 z = y.toInt128(); + assertEq(z, 0); + uint128 x = z.toUint128(); + assertEq(x, 0); + } + + function testRevert_cast_large_uint() public { + uint128 y = 1 << 127; + vm.expectRevert(abi.encodeWithSelector(SafeCastHelper.SafeCastOverflow.selector)); + int128 z = y.toInt128(); + z = z; // avoid warning, never reach here + } + + function testRevert_cast_minus_int() public { + int128 y = -1; + vm.expectRevert(abi.encodeWithSelector(SafeCastHelper.SafeCastUnderflow.selector)); + uint128 z = y.toUint128(); + z = z; // avoid warning, never reach here + } + + function test_abs_positive() public { + int128 x = 123; + uint128 y = x.abs(); + assertEq(y, 123); + } + + function test_abs_negative() public { + int128 x = -123; + uint128 y = x.abs(); + assertEq(y, 123); + } + + function test_abs_min_int() public { + int128 x = type(int128).min; + uint128 y = x.abs(); + assertEq(y, 1 << 127); + } + + function test_abs_i256_positive() public { + int256 x = 123; + uint256 y = x.abs_i256(); + assertEq(y, 123); + } + + function test_abs_i256_negative() public { + int256 x = -123; + uint256 y = x.abs_i256(); + assertEq(y, 123); + } + + function test_abs_i256_min_int() public { + int256 x = type(int256).min; + uint256 y = x.abs_i256(); + assertEq(y, 1 << 255); + } +}