├── .DS_Store ├── .gas-snapshot ├── .github └── workflows │ └── test.yml ├── .gitignore ├── .gitmodules ├── README.md ├── audits └── Solidproof_Audit_Report.pdf ├── foundry.toml ├── src ├── aggregators │ ├── gmx │ │ ├── Config.sol │ │ ├── GmxAdapter.sol │ │ ├── Position.sol │ │ ├── Storage.sol │ │ ├── Types.sol │ │ └── lib │ │ │ └── LibGmx.sol │ ├── lib │ │ ├── LibMath.sol │ │ └── LibUtils.sol │ └── mux │ │ ├── Config.sol │ │ ├── MuxAdapter.sol │ │ ├── Position.sol │ │ ├── Storage.sol │ │ ├── Types.sol │ │ └── lib │ │ └── LibMux.sol ├── components │ └── ImplementationGuard.sol ├── gmxProxyFactory │ ├── GmxProxyBeacon.sol │ ├── GmxProxyConfig.sol │ ├── GmxProxyFactory.sol │ └── GmxStorage.sol ├── interfaces │ ├── IGmxAggregator.sol │ ├── IGmxOrderBook.sol │ ├── IGmxPositionRouter.sol │ ├── IGmxProxyFactory.sol │ ├── IGmxRouter.sol │ ├── IGmxVault.sol │ ├── IMuxAggregator.sol │ ├── IMuxGetter.sol │ ├── IMuxOrderBook.sol │ ├── IMuxProxyFactory.sol │ ├── ITransparentUpgradeableProxy.sol │ └── IWETH.sol ├── muxProxyFactory │ ├── MuxProxyBeacon.sol │ ├── MuxProxyConfig.sol │ ├── MuxProxyFactory.sol │ └── MuxStorage.sol └── transparentUpgradeableProxy │ └── TransparentUpgradeableProxy.sol └── test ├── test_gmxAdapter.sol ├── test_gmxProxyFactory.sol ├── test_gmxSetUp.sol ├── test_muxAdapter.sol ├── test_muxProxyFactory.sol └── test_muxSetUp.sol /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugenix-io/logx-aggregator-contracts/d8070a51627081b079a7661d5558e7e8707b5308/.DS_Store -------------------------------------------------------------------------------- /.gas-snapshot: -------------------------------------------------------------------------------- 1 | TestGmxAdapter:testGmxAdapterClosePosition() (gas: 1049844) 2 | TestGmxAdapter:testGmxAdapterGetPositionKey() (gas: 15164) 3 | TestGmxAdapter:testGmxAdapterInitialization() (gas: 137447) 4 | TestGmxAdapter:testGmxAdapterOpenPosition() (gas: 1405293) 5 | TestGmxAdapter:testGmxAdapterTPSLOrders() (gas: 1394286) 6 | TestGmxAdapter:testGmxAdapterWithdraw() (gas: 77268) 7 | TestMuxAdapter:testMuxAdapterClosePosition() (gas: 415556) 8 | TestMuxAdapter:testMuxAdapterGetSubAccountId() (gas: 12865) 9 | TestMuxAdapter:testMuxAdapterInitialization() (gas: 135078) 10 | TestMuxAdapter:testMuxAdapterOpenPosition() (gas: 720395) 11 | TestMuxAdapter:testMuxAdapterWithdraw() (gas: 70324) 12 | TestProxyFactory:testGmxGetConfigVersions() (gas: 250468) 13 | TestProxyFactory:testGmxInitialize() (gas: 13029) 14 | TestProxyFactory:testGmxSetExchangeConfig() (gas: 490416) 15 | TestProxyFactory:testGmxSetMaintainer() (gas: 16521) 16 | TestProxyFactory:testGmxUpgradeTo() (gas: 18822) 17 | TestProxyFactory:testMuxGetConfigVersions() (gas: 122002) 18 | TestProxyFactory:testMuxInitialize() (gas: 13029) 19 | TestProxyFactory:testMuxSetExchangeConfig() (gas: 235698) 20 | TestProxyFactory:testMuxSetMaintainer() (gas: 16550) 21 | TestProxyFactory:testMuxUpgradeTo() (gas: 18825) -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: workflow_dispatch 4 | 5 | env: 6 | FOUNDRY_PROFILE: ci 7 | 8 | jobs: 9 | check: 10 | strategy: 11 | fail-fast: true 12 | 13 | name: Foundry project 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v3 17 | with: 18 | submodules: recursive 19 | 20 | - name: Install Foundry 21 | uses: foundry-rs/foundry-toolchain@v1 22 | with: 23 | version: nightly 24 | 25 | - name: Run Forge build 26 | run: | 27 | forge --version 28 | forge build --sizes 29 | id: build 30 | 31 | - name: Run Forge tests 32 | run: | 33 | forge test -vvv 34 | id: test 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiler files 2 | cache/ 3 | out/ 4 | 5 | # Ignores development broadcast logs 6 | !/broadcast 7 | /broadcast/*/31337/ 8 | /broadcast/**/dry-run/ 9 | 10 | # Docs 11 | docs/ 12 | 13 | # Dotenv file 14 | .env 15 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/forge-std"] 2 | path = lib/forge-std 3 | url = https://github.com/foundry-rs/forge-std 4 | [submodule "lib/openzeppelin-contracts"] 5 | path = lib/openzeppelin-contracts 6 | url = https://github.com/openzeppelin/openzeppelin-contracts 7 | [submodule "lib/openzeppelin-contracts-upgradeable"] 8 | path = lib/openzeppelin-contracts-upgradeable 9 | url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LogX Aggregator 2 | 3 | LogX is a perpetual trading aggregator which allows users to seamlessly get the best trade on multiple perpetual trading exchanges across chains. 4 | 5 | ## Overview 6 | 7 | `Proxy Factory` is the contract with external functions for users/UI to interact with. For every position a user opens from a `Proxy Factory`, an `exchange adapter` (or `Exchange Adapter`) contract will be deployed which will, in turn, open a position on the exchange for the user. Any subsequent modifications to the position (placing an order, modifying the order, closing the position, etc) will be handled via the same `exchange adapter` contract. 8 | 9 | ## MUX Proxy Factory User Trade Functions 10 | 11 | ### Open and Close Positions 12 | ```solidity 13 | function openPosition(PositionArgs calldata args, PositionOrderExtra calldata extra) external payable 14 | function closePosition(PositionArgs calldata args, PositionOrderExtra calldata extra) external payable 15 | ``` 16 | Note - in case of MUX, PositionArgs are common for opening and closing positions - both these functions internally call ‘placePositionOrder()’ on exchange adapter. 17 | We still have two separate functions for opening and closing positions instead of one to maintain consistency with the GMX interface. 18 | 19 | #### PositionArgs 20 | ```solidity 21 | struct PositionArgs { 22 | uint256 exchangeId; 23 | address collateralToken; 24 | uint8 collateralId; 25 | uint8 assetId; 26 | uint8 profitTokenId; 27 | bool isLong; 28 | uint96 collateralAmount; // tokenIn.decimals 29 | uint96 size; // 1e18 30 | uint96 price; // 1e18 31 | uint96 collateralPrice; 32 | uint96 assetPrice; 33 | uint8 flags; // MARKET, TRIGGER 34 | bytes32 referralCode; 35 | uint32 deadline; 36 | } 37 | ``` 38 | 1. **exchangeId :** every exchange on logX aggregator has its own exchange ID. Used for creating an exchange adapter for the user's position. Currently, exchange ID for GMX is 0 and MUX is 1 39 | 2. **collateralToken :** address of the token being used as collateral for the position. Used for creating an exchange adapter for the user's position. In the case of MUX, this is the token which will be deposited as collateral by the user (which is not the case with GMX - we will look into that shortly in this documentation). If the address supplied is not supported by the exchange, it might cause a revert while opening the position, therefore, the user has to make sure the collateral address supplied here is supported by the exchange. 40 | 3. **collateralId :** MUX ID of the token being used as collateral for the position. 41 | 3. **assetId :** MUX ID of the token being used as asset for the exchange. Used for creating an exchange adapter for the user's position. If the address supplied is not supported by the exchange, it might cause a revert while opening the position, therefore, the user has to make sure the collateral address supplied here is supported by the exchange. 42 | 4. **profitToken :** address of the token in which the user wants to redeem profits. This address is only used while closing a Position. While opening a position on MUX, the profitToken will automatically be set to ‘0’ to avoid reverting on side of MUX. 43 | 5. **isLong :** true if user wants to enter a long position else false. 44 | 6. **collateralAmount :** Amount of the ‘collateralToken’ the user wants to deposit for opening the position. Collateral Amount for MUX Adapter should be denominated in terms of the decimals of the collateral token. 45 | ```Example - if the user wants to long 20 DAI, the collateral amount should be 20 X (10 ^ DAI Decimals)``` 46 | 7. **size :** position size. Size has to be denominated in 1e18 and has to be a USD value. 47 | ```Example : if the user wants to long 2BTC, the size of the position will be 2 * price of BTC * (10 ^ 18)``` 48 | 8. **price :** price of the limit or trigger order. Price has to be denominated in 1e18 and has to be a USD value. 49 | ```Example : if the user wants to set a limit price of 1900 ETH, the price of the position will be 1900 * (10 ^ 18)``` 50 | 9. **collateralPrice :** price of the collateral Token in USD - this value is not passed to MUX, but rather used by the exchange adapter (or Exchange Adapter) to check if the Margin of the position is safe. Collateral Price has to be denominated in 1e18 and has to be a USD value. 51 | ```Example : if the price of the collateral token is $150, the collateralPrice for the position will be 150 * (10 ^ 18)``` 52 | 10. **assetPrice :** price of the underlying Token in USD - this value is not passed to MUX, but rather used by the exchange adapter (or Exchange Adapter) to check if the Margin of the position is safe. Collateral Price has to be denominated in 1e18 and has to be a USD value. 53 | ```Example : if the price of the asset token is $500, the collateralPrice for the position will be 500 * (10 ^ 18)``` 54 | 11. **flags :** flags will be used to determine the position the user wants to enter - the same value will be passed on to MUX OrderBook. 55 | Following are the different types of Flags - 56 | ``` 57 | POSITION_OPEN = 0x80; // this flag means openPosition; otherwise closePosition 58 | POSITION_MARKET_ORDER = 0x40; // this flag means ignore limitPrice 59 | POSITION_WITHDRAW_ALL_IF_EMPTY = 0x20; // this flag means auto withdraw all collateral if position.size == 0 60 | POSITION_TRIGGER_ORDER = 0x10; // this flag means this is a trigger order (ex: stop-loss order). otherwise this is a limit order (ex: take-profit order) 61 | POSITION_TPSL_STRATEGY = 0x08; // for open-position-order, this flag auto place take-profit and stop-loss orders when open-position-order fills.for close-position-order, this flag means ignore limitPrice and profitTokenId, and use extra.tpPrice, extra.slPrice, extra.tpslProfitTokenId instead.` 62 | Example : If the user wants to open a market order, the flags will be 0x80 + 0x40. 63 | ``` 64 | **NOTE on limit and trigger orders :** 65 | Limit Order is Take Profit order while Trigger order is a Stop Loss Order. 66 | Following is the table on how the price of these orders are related to tradingPrice of the asset - 67 | ``` 68 | open,long 0,0 0,1 1,1 1,0 69 | limitOrder <= >= <= >= 70 | triggerOrder >= <= >= <= 71 | Example : if the user opens a long position for limit order, the tradingPrice <= limitOrderPrice. If the user opens a short position for trigger order, the tradingPrice <= triggerOrderPrice. 72 | ``` 73 | 74 | **NOTE on TPSL strategy :** 75 | On MUX, if the user wants to close a position with TPSL strategy, the following additional conditions should be met - 76 | ``` 77 | price == 0 (since tpsl extra parameter price will be used) 78 | collateralAmount == 0 (tp/sl strategy only support POSITION_WITHDRAW_ALL_IF_EMPTY and we cannot add more collateral while placing a close Position TPSL order) 79 | profitTokenId == 0 (profit token ID mentioned in the tpsl extra parameters will be used) flags cannot have MARKET_ORDER. (Since TPSL for close does not support market orders). 80 | ``` 81 | 12. **referralCode :** following parameter is not being used since we inject logX referral code into exchange adapter. For now this value can be null. 82 | 13. **deadline :** deadline before the order expires. Deadline should be 0 for Market Orders - even if the user give a deadline for a market order by mistake, the proxy factory makes sure the deadline for market orders is set to 0. 83 | 14. **msg.value :** if the collateralToken is _weth, then the msg.value should be equal to collateralAmount. If collateralToken is not _weth, then msg.value should be equal to 0. 84 | 85 | #### PositionOrderExtra 86 | ```solidity 87 | struct PositionOrderExtra { 88 | // tp/sl strategy 89 | uint96 tpPrice; // take-profit price. decimals = 18. only valid when flags.POSITION_TPSL_STRATEGY. 90 | uint96 slPrice; // stop-loss price. decimals = 18. only valid when flags.POSITION_TPSL_STRATEGY. 91 | uint8 tpslProfitTokenId; // only valid when flags.POSITION_TPSL_STRATEGY. 92 | uint32 tpslDeadline; // only valid when flags.POSITION_TPSL_STRATEGY. 93 | } 94 | ``` 95 | The arguments above are self explainatory. 96 | 97 | ### Order Management 98 | ```solidity 99 | function getPendingOrderKeys(uint256 exchangeId, address collateralToken, address assetToken, bool isLong) external view returns(uint64[] memory) 100 | function cancelOrders(uint256 exchangeId, address collateralToken, address assetToken, bool isLong, uint64[] calldata keys) external 101 | ``` 102 | **keys :** list of the order keys we want to cancel 103 | 104 | ## GMX Proxy Factory User Trade Functions 105 | 106 | Unlike MUX, GMX open and close Position function have different input arguments. 107 | ### Open Position 108 | ```solidity 109 | function openPosition(OpenPositionArgs calldata args) external payable 110 | ``` 111 | 112 | #### OpenPositionArgs 113 | ```solidity 114 | struct OpenPositionArgs { 115 | uint256 exchangeId; 116 | address collateralToken; 117 | address assetToken; 118 | bool isLong; 119 | address tokenIn; 120 | uint256 amountIn; // tokenIn.decimals 121 | uint256 minOut; // collateral.decimals 122 | uint256 sizeUsd; // 1e18 123 | uint96 priceUsd; // 1e18 124 | uint96 tpPriceUsd; // 1e18 125 | uint96 slPriceUsd; // 1e18 126 | uint8 flags; // MARKET, TRIGGER 127 | bytes32 referralCode; 128 | } 129 | ``` 130 | 1. **exchangeId :** every exchange on logX aggregator has its own exchange ID. Used for creating an exchange adapter for the user's position. Currently, exchange ID for GMX is 0 and MUX is 1 131 | 2. **collateralTOken :** address of the token being used as collateral for the position. Used for creating an exchange adapter for the user's position. For a GMX Long Position, the collateral Token has to be same as assetToken, and collateral token cannot be a stable coin. For a Short Position, the collateral Token HAS to be a stable coin. We can determine which stable coin to use for a Short Position based on current swap fees between swapInToken and collateralToken. 132 | 3. **assetToken :** address of the token being used as underlying / asset for the position. Used for creating an exchange adapter for the user's position. For GMX Long Position, the collateralToken and the indexToken have to be the same. For GMX Short Position, the index token should NOT be a stable coin and it has to be shortable. 133 | 4. **isLong :** true if user wants to enter a long position else false. 134 | 5. **tokenIn :** address of the token which user will be depositing to the exchange as collateral. The difference between tokenIn and collateralToken is that, tokenIn will be swapped with collateralToken in order to open a GMX Position to follow the rules mentioned in 2. 135 | 6. **amountIn :** the amount of 'tokenIn' the user will be depositing as a collateral. amountIn has to be denominated in terms of the decimal of tokenIn. ```Example: If the user wants to deposit 2 WBTC as tokenIn, then amountIn will be 2*(10 ^ WBTC decimals) ``` 136 | 7. **minOut :** minimum number of collateralToken to be received after swapping tokenIn with collateralToken. minOut has to be denominated in terms of the decimals of collateralToken. ```Example: If the user's collateralToken is USDC and tokenIn is WETH, then minOut has to be denominated in terms of the number of decimals of USDC.``` 137 | 8. **sizeUsd :** size (in USD) of the position the user wants to enter. sizeUsd has to be denominated in 1e18. 138 | 9. **priceUsd :** limit price at which the position can be opened. priceUsd has to be denominated in 1e18. 139 | 10. **tpPriceUsd :** take profit price. tpPriceUsd has to be denominated in 1e18. NOTE that TPSL strategy is a closing order placed on GMX - it will take effect at the time of closing the position, not opening. 140 | 11. **slPriceUsd :** stop loss price. slPriceUsd has to be denominated in 1e18. 141 | NOTE - in GMX, all USD values - price, size, etc (even for closePosition()) are denominated in 1e30 (not 1e18) so we will be adding the extra 12 decimals in exchange adapter. This is being done to maintain consistency for orders being placed via logX. 142 | 12. **flags :** flags will be used to determine the position the user wants to enter - the same value will NOT be passed on to GMX OrderBook. 143 | Following are the different types of Flags - 144 | ``` 145 | POSITION_MARKET_ORDER = 0x40 146 | POSITION_TPSL_ORDER = 0x08 147 | Example : If the user wants to open a tpsl order, the flags will be 0x08. 148 | ``` 149 | 12. **referralCode :** following parameter is not being used since we inject logX referral code into exchange adapter. For now this value can be null. 150 | 13. **msg.value :** In GMX, the msg.value should have the executionFees which GMX charges. Everytime the user places a market / limit order in open / close position. If the msg.value is less than minimum execution fee of GMX, the transaction fails to go through. 151 | NOTE that while placing a TPSL order, the user will be paying the execution Fees thrice (once to open position, once to create TP close order, once to create SL close order). In all other cases, the user will pay execution Fees only once. 152 | If the collateralToken is specified as WETH, the msg.value should have amountIn + executionFees amount of ETH failing which the transaction will not go through. 153 | 154 | ### Close Position 155 | ```solidity 156 | function closePosition(ClosePositionArgs calldata args) external payable 157 | ``` 158 | 159 | #### ClosePositionArgs 160 | ```solidity 161 | struct ClosePositionArgs { 162 | uint256 exchangeId; 163 | address collateralToken; 164 | address assetToken; 165 | bool isLong; 166 | uint256 collateralUsd; // collateral.decimals 167 | uint256 sizeUsd; // 1e18 168 | uint96 priceUsd; // 1e18 169 | uint96 tpPriceUsd; // 1e18 170 | uint96 slPriceUsd; // 1e18 171 | uint8 flags; // MARKET, TRIGGER 172 | bytes32 referralCode; 173 | } 174 | ``` 175 | **NOTE -** the following documentation only talks about the arguments which have not been talked about in OpenPositionArgs. If an argument is present in OpenPositionArgs and not discussed, the reader should assume the same applied here. 176 | 177 | 1. **collateralUsd :** collateral amount in USD that the user wants to withdraw / decrease from the position. collateralUsd has to be denominated in 1e18. 178 | 2. **sizeUsd :** size of the position in USD that the user wants to withdraw / decrease from the position. sizeUsd has to be denominated in 1e18. 179 | 180 | ### Order Management 181 | ```solidity 182 | function cancelOrders(uint256 exchangeId, address collateralToken, address assetToken, bool isLong, bytes32[] calldata keys) external 183 | function updateOrder(uint256 exchangeId, address collateralToken, address assetToken, bool isLong, OrderParams[] memory orderParams) external 184 | function getPendingOrderKeys(uint256 exchangeId, address collateralToken, address assetToken, bool isLong) external view returns(bytes32[] memory) 185 | ``` 186 | **keys :** list of the order keys we want to cancel 187 | 188 | #### OrderParams 189 | ```solidity 190 | struct OrderParams { 191 | bytes32 orderKey; 192 | uint256 collateralDelta; 193 | uint256 sizeDelta; 194 | uint256 triggerPrice; 195 | bool triggerAboveThreshold; 196 | } 197 | ``` 198 | 1. **orderKey :** key of the order we want to update. 199 | 2. **collateralDelta :** additional increase / decrease in collateral amount for position. collateralDelta has to be denominated in 1e18. 200 | 2. **sizeDelta :** additional increase / decrease in size for position. sizeDelta has to be denominated in 1e18. 201 | 2. **triggerPrice :** new trigger price for order. triggerPrice has to be denominated in 1e18. 202 | 2. **triggerAboveThreshold :** true if order has to be triggered if the price is above the threshold and vice versa. 203 | 204 | ## Post Trade Functions 205 | 206 | Following part of the document deals with functions that can be used to track user's positions and orders after they have placed a transactions 207 | 208 | ### Fetching User Position 209 | 210 | Each Position is represented by an Adapter Proxy contract - any subsequent changes to the same position will be reflected in the contract. 211 | 212 | To get the addresses of all adapter proxies belonging to a user 213 | ```solidity 214 | function getProxiesOf(address account) public view returns (address[] memory) 215 | ``` 216 | 217 | We can call the following function in each of the proxy addresses to get position information 218 | ```solidity 219 | function accountState() external view returns (AccountState memory) 220 | ``` 221 | 222 | #### AccountState 223 | ```solidity 224 | struct AccountState { 225 | address account; 226 | address collateralToken; 227 | address indexToken; // 160 228 | bool isLong; // 8 229 | uint8 collateralDecimals; 230 | bytes32[20] reserved; 231 | } 232 | ``` 233 | **account :** address of the user who owns the position 234 | Remaining parameters are self explainatory. 235 | 236 | For additional information regarding the position, we will have to directly call functions of exchange's contract. 237 | In order to get position details directly from exchange's contracts, we will need subAccountId and gmxPositionKey for MUX and GMX respectively. 238 | 239 | #### MUX Position Details 240 | 241 | Following function can be called in the adapter proxy to get the subAccountId associated with the position - 242 | ```solidity 243 | function getSubAccountId() external view returns(bytes32) 244 | ``` 245 | 246 | Once we get the subAccountId we can call the following function from MUX Getter (Liquidity Pool) to fetch more details regarding the subAccount 247 | ```solidity 248 | function getSubAccount( 249 | bytes32 subAccountId 250 | ) 251 | external 252 | view 253 | returns (uint96 collateral, uint96 size, uint32 lastIncreasedTime, uint96 entryPrice, uint128 entryFunding) 254 | ``` 255 | 256 | #### GMX Position Details 257 | 258 | Following function can be called in the adapter proxy to get the gmxPositionKey associated with the position - 259 | ```solidity 260 | function getPositionKey() external view returns(bytes32) 261 | ``` 262 | 263 | Once we get the gmxPositionKey we can call the following function from GMX Vault to fetch more details regarding the position 264 | ```solidity 265 | function getPosition(address _account, address _collateralToken, address _indexToken, bool _isLong) public override view returns (uint256, uint256, uint256, uint256, uint256, uint256, bool, uint256) 266 | // 0 size, 267 | // 1 collateral, 268 | // 2 averagePrice, 269 | // 3 entryFundingRate, 270 | // 4 reserveAmount, 271 | // 5 realisedPnl, 272 | // 6 realisedPnl >= 0, 273 | // 7 lastIncreasedTime 274 | ``` 275 | 276 | ### Fetching User Orders 277 | 278 | We can get the pending order keys of a user by querying the following function with the adapter proxy - 279 | ```solidity 280 | For MUX - 281 | function getPendingOrderKeys() external view returns (uint64[] memory) 282 | 283 | For GMX - 284 | function getPendingOrderKeys() external view returns (bytes32[] memory) 285 | 286 | Note the difference in datatypes of the return values 287 | ``` 288 | 289 | Once we get the order keys, in case of **GMX** we can query another function on our adapter proxy to fetch more information - 290 | ```solidity 291 | function getOrder(bytes32 orderKey) external view returns(bool isFilled, LibGmx.OrderHistory memory history) 292 | ``` 293 | 294 | #### Order History, Order Category and Order Receiver 295 | ```solidity 296 | struct OrderHistory { 297 | OrderCategory category; // 4 298 | OrderReceiver receiver; // 4 299 | uint64 index; // 64 300 | uint96 zero; // 96 301 | uint88 timestamp; // 80 302 | } 303 | 304 | enum OrderCategory { 305 | NONE, 306 | OPEN, 307 | CLOSE 308 | } 309 | 310 | enum OrderReceiver { 311 | PR_INC, 312 | PR_DEC, 313 | OB_INC, 314 | OB_DEC 315 | } 316 | ``` 317 | 1. **index :** GMX Order Index 318 | 2. **zero :** Empty space left for potential future use 319 | 3. **timestamp :** timestamp at which the order was placed 320 | 4. **PR_INC :** Increase Position Order plaed with Position Router (Market Order) 321 | 5. **PR_DEC :** Decrease Position Order plaed with Position Router (Market Order) 322 | 6. **OB_INC :** Increase Position Order plaed with Order Book (Limit Order) 323 | 7. **OB_DEC :** Decrease Position Order plaed with Order Book (Limit Order) 324 | 325 | In case of **MUX** we have to call the following function directly from MUX Order Book- 326 | ```solidity 327 | function getOrder(uint64 orderId) external view returns (bytes32[3] memory, bool) 328 | ``` 329 | Following is how the bytes32[3] return value looks like (The response has to be decoded) - 330 | ``` 331 | // 160 152 144 120 96 72 64 8 0 332 | // +----------------------------------------------------------------------------------+--------------------+--------+ 333 | // | subAccountId 184 (already shifted by 72bits) | orderId 64 | type 8 | 334 | // +----------------------------------+----------+---------+-----------+---------+---------+---------------+--------+ 335 | // | size 96 | profit 8 | flags 8 | unused 24 | exp 24 | time 32 | enumIndex 64 | 336 | // +----------------------------------+----------+---------+-----------+---------+---------+---------------+--------+ 337 | // | price 96 | collateral 96 | unused 64 | 338 | // +----------------------------------+----------------------------------------------------+------------------------+ 339 | ``` -------------------------------------------------------------------------------- /audits/Solidproof_Audit_Report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugenix-io/logx-aggregator-contracts/d8070a51627081b079a7661d5558e7e8707b5308/audits/Solidproof_Audit_Report.pdf -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "src" 3 | out = "out" 4 | libs = ["lib"] 5 | 6 | # See more config options https://github.com/foundry-rs/foundry/tree/master/config -------------------------------------------------------------------------------- /src/aggregators/gmx/Config.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity 0.8.19; 3 | 4 | import "../../../lib/openzeppelin-contracts-upgradeable/contracts/token/ERC20/extensions/IERC20MetadataUpgradeable.sol"; 5 | import "../../../lib/openzeppelin-contracts-upgradeable/contracts/utils/structs/EnumerableSetUpgradeable.sol"; 6 | 7 | import "../../interfaces/IGmxProxyFactory.sol"; 8 | 9 | import "../lib/LibUtils.sol"; 10 | import "./Storage.sol"; 11 | import "./Position.sol"; 12 | 13 | contract Config is Storage, Position{ 14 | using LibUtils for bytes32; 15 | using LibUtils for address; 16 | using LibUtils for uint256; 17 | using EnumerableSetUpgradeable for EnumerableSetUpgradeable.Bytes32Set; 18 | 19 | function _updateConfigs() internal virtual{ 20 | (uint32 latestexchangeVersion) = IGmxProxyFactory(_factory).getConfigVersions( 21 | EXCHANGE_ID 22 | ); 23 | if (_localexchangeVersion < latestexchangeVersion) { 24 | _updateExchangeConfigs(); 25 | _localexchangeVersion = latestexchangeVersion; 26 | } 27 | } 28 | 29 | function _updateExchangeConfigs() internal { 30 | uint256[] memory values = IGmxProxyFactory(_factory).getExchangeConfig(EXCHANGE_ID); 31 | require(values.length >= uint256(ExchangeConfigIds.END), "MissingConfigs"); 32 | 33 | address newPositionRouter = values[uint256(ExchangeConfigIds.POSITION_ROUTER)].toAddress(); 34 | address newOrderBook = values[uint256(ExchangeConfigIds.ORDER_BOOK)].toAddress(); 35 | //ToDo - is cancelling orders when we change positionRouter and orderBook really necessary? 36 | _onGmxAddressUpdated( 37 | _exchangeConfigs.positionRouter, 38 | _exchangeConfigs.orderBook, 39 | newPositionRouter, 40 | newOrderBook 41 | ); 42 | _exchangeConfigs.vault = values[uint256(ExchangeConfigIds.VAULT)].toAddress(); 43 | _exchangeConfigs.positionRouter = newPositionRouter; 44 | _exchangeConfigs.orderBook = newOrderBook; 45 | _exchangeConfigs.router = values[uint256(ExchangeConfigIds.ROUTER)].toAddress(); 46 | _exchangeConfigs.referralCode = bytes32(values[uint256(ExchangeConfigIds.REFERRAL_CODE)]); 47 | _exchangeConfigs.marketOrderTimeoutSeconds = values[uint256(ExchangeConfigIds.MARKET_ORDER_TIMEOUT_SECONDS)] 48 | .toU32(); 49 | _exchangeConfigs.limitOrderTimeoutSeconds = values[uint256(ExchangeConfigIds.LIMIT_ORDER_TIMEOUT_SECONDS)] 50 | .toU32(); 51 | _exchangeConfigs.initialMarginRate = values[uint256(ExchangeConfigIds.INITIAL_MARGIN_RATE)] 52 | .toU32(); 53 | _exchangeConfigs.maintenanceMarginRate = values[uint256(ExchangeConfigIds.MAINTENANCE_MARGIN_RATE)] 54 | .toU32(); 55 | } 56 | 57 | function _onGmxAddressUpdated( 58 | address previousPositionRouter, 59 | address previousOrderBook, 60 | address newPostitionRouter, 61 | address newOrderBook 62 | ) internal virtual { 63 | bool cancelPositionRouter = previousPositionRouter != newPostitionRouter; 64 | bool cancelOrderBook = previousOrderBook != newOrderBook; 65 | bytes32[] memory pendingKeys = _pendingOrders.values(); 66 | for (uint256 i = 0; i < pendingKeys.length; i++) { 67 | bytes32 key = pendingKeys[i]; 68 | if (cancelPositionRouter) { 69 | LibGmx.cancelOrderFromPositionRouter(previousPositionRouter, key); 70 | _removePendingOrder(key); 71 | } 72 | if (cancelOrderBook) { 73 | LibGmx.cancelOrderFromOrderBook(previousOrderBook, key); 74 | _removePendingOrder(key); 75 | } 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /src/aggregators/gmx/GmxAdapter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity 0.8.19; 3 | 4 | import "../../../lib/openzeppelin-contracts-upgradeable/contracts/utils/structs/EnumerableSetUpgradeable.sol"; 5 | import "../../../lib/openzeppelin-contracts-upgradeable/contracts/utils/math/MathUpgradeable.sol"; 6 | import "../../../lib/openzeppelin-contracts-upgradeable/contracts/token/ERC20/utils/SafeERC20Upgradeable.sol"; 7 | import "../../../lib/openzeppelin-contracts-upgradeable/contracts/token/ERC20/extensions/IERC20MetadataUpgradeable.sol"; 8 | import "../../../lib/openzeppelin-contracts-upgradeable/contracts/security/ReentrancyGuardUpgradeable.sol"; 9 | 10 | import "../../interfaces/IGmxRouter.sol"; 11 | 12 | import "../../interfaces/IWETH.sol"; 13 | import "../../components/ImplementationGuard.sol"; 14 | import "./Storage.sol"; 15 | import "./Config.sol"; 16 | import "./Position.sol"; 17 | 18 | contract GMXAdapter is Position, Config, ImplementationGuard, ReentrancyGuardUpgradeable{ 19 | using MathUpgradeable for uint256; 20 | using SafeERC20Upgradeable for IERC20Upgradeable; 21 | using EnumerableSetUpgradeable for EnumerableSetUpgradeable.Bytes32Set; 22 | using EnumerableMapUpgradeable for EnumerableMapUpgradeable.Bytes32ToBytes32Map; 23 | 24 | address internal immutable _WETH; 25 | 26 | event Withdraw( 27 | address collateralAddress, 28 | address account, 29 | uint256 balance 30 | ); 31 | 32 | constructor(address weth) ImplementationGuard() { 33 | _WETH = weth; 34 | } 35 | 36 | receive() external payable {} 37 | 38 | modifier onlyTraderOrFactory() { 39 | require(msg.sender == _account.account || msg.sender == _factory, "OnlyTraderOrFactory"); 40 | _; 41 | } 42 | 43 | modifier onlyFactory() { 44 | require(msg.sender == _factory, "onlyFactory"); 45 | _; 46 | } 47 | 48 | function initialize( 49 | uint256 exchangeId, 50 | address account, 51 | address collateralToken, 52 | address assetToken, 53 | bool isLong 54 | ) external initializer onlyDelegateCall { 55 | require(exchangeId == EXCHANGE_ID, "Invalidexchange"); 56 | 57 | _factory = msg.sender; 58 | _gmxPositionKey = keccak256(abi.encodePacked(address(this), collateralToken, assetToken, isLong)); 59 | 60 | _account.account = account; 61 | _account.collateralToken = collateralToken; 62 | _account.indexToken = assetToken; 63 | _account.isLong = isLong; 64 | _account.collateralDecimals = IERC20MetadataUpgradeable(collateralToken).decimals(); 65 | _updateConfigs(); 66 | } 67 | 68 | function accountState() external view returns (AccountState memory) { 69 | return _account; 70 | } 71 | 72 | function getPendingOrderKeys() external view returns (bytes32[] memory) { 73 | return _getPendingOrders(); 74 | } 75 | 76 | function getPositionKey() external view returns(bytes32){ 77 | return _gmxPositionKey; 78 | } 79 | 80 | function getOrder(bytes32 orderKey) external view returns(bool isFilled, LibGmx.OrderHistory memory history){ 81 | (isFilled, history) = LibGmx.getOrder(_exchangeConfigs, orderKey); 82 | } 83 | 84 | function _tryApprovePlugins() internal { 85 | IGmxRouter(_exchangeConfigs.router).approvePlugin(_exchangeConfigs.orderBook); 86 | IGmxRouter(_exchangeConfigs.router).approvePlugin(_exchangeConfigs.positionRouter); 87 | } 88 | 89 | function _isMarketOrder(uint8 flags) internal pure returns (bool) { 90 | return (flags & POSITION_MARKET_ORDER) != 0; 91 | } 92 | 93 | function openPosition( 94 | address swapInToken, 95 | uint256 swapInAmount, // tokenIn.decimals 96 | uint256 minSwapOut, // collateral.decimals 97 | uint256 sizeUsd, // 1e18 98 | uint96 priceUsd, // 1e18 99 | uint96 tpPriceUsd, 100 | uint96 slPriceUsd, 101 | uint8 flags // MARKET, TRIGGER 102 | ) external payable onlyTraderOrFactory nonReentrant { 103 | 104 | _updateConfigs(); 105 | _tryApprovePlugins(); 106 | _cleanOrders(); 107 | 108 | bytes32 orderKey; 109 | orderKey = _openPosition( 110 | swapInToken, 111 | swapInAmount, // tokenIn.decimals 112 | minSwapOut, // collateral.decimals 113 | sizeUsd, // 1e18 114 | priceUsd, // 1e18 115 | flags // MARKET, TRIGGER 116 | ); 117 | 118 | if (flags & POSITION_TPSL_ORDER > 0) { 119 | bytes32 tpOrderKey; 120 | bytes32 slOrderKey; 121 | if (tpPriceUsd > 0) { 122 | tpOrderKey = _closePosition(0, sizeUsd, tpPriceUsd, 0); 123 | } 124 | if (slPriceUsd > 0) { 125 | slOrderKey = _closePosition(0, sizeUsd, slPriceUsd, 0); 126 | } 127 | _openTpslOrderIndexes.set(orderKey, LibGmx.encodeTpslIndex(tpOrderKey, slOrderKey)); 128 | } 129 | } 130 | 131 | function _openPosition( 132 | address swapInToken, 133 | uint256 swapInAmount, // tokenIn.decimals 134 | uint256 minSwapOut, // collateral.decimals 135 | uint256 sizeUsd, // 1e18 136 | uint96 priceUsd, // 1e18 137 | uint8 flags // MARKET, TRIGGER 138 | ) internal returns(bytes32 orderKey){ 139 | 140 | _updateConfigs(); 141 | _tryApprovePlugins(); 142 | _cleanOrders(); 143 | 144 | OpenPositionContext memory context = OpenPositionContext({ 145 | sizeUsd: sizeUsd * GMX_DECIMAL_MULTIPLIER, 146 | priceUsd: priceUsd * GMX_DECIMAL_MULTIPLIER, 147 | isMarket: _isMarketOrder(flags), 148 | fee: 0, 149 | amountIn: 0, 150 | amountOut: 0, 151 | gmxOrderIndex: 0, 152 | executionFee: msg.value 153 | }); 154 | if (swapInToken == _WETH) { 155 | IWETH(_WETH).deposit{ value: swapInAmount }(); 156 | context.executionFee = msg.value - swapInAmount; 157 | } 158 | if (swapInToken != _account.collateralToken) { 159 | context.amountOut = LibGmx.swap( 160 | _exchangeConfigs, 161 | swapInToken, 162 | _account.collateralToken, 163 | swapInAmount, 164 | minSwapOut 165 | ); 166 | } else { 167 | context.amountOut = swapInAmount; 168 | } 169 | context.amountIn = context.amountOut; 170 | IERC20Upgradeable(_account.collateralToken).approve(_exchangeConfigs.router, context.amountIn); 171 | 172 | return _openPosition(context); 173 | } 174 | 175 | /// @notice Place a closing request on GMX. 176 | function closePosition( 177 | uint256 collateralUsd, // collateral.decimals 178 | uint256 sizeUsd, // 1e18 179 | uint96 priceUsd, // 1e18 180 | uint96 tpPriceUsd, // 1e18 181 | uint96 slPriceUsd, // 1e18 182 | uint8 flags // MARKET, TRIGGER 183 | ) external payable onlyTraderOrFactory nonReentrant { 184 | 185 | _updateConfigs(); 186 | _cleanOrders(); 187 | 188 | if (flags & POSITION_TPSL_ORDER > 0) { 189 | if (_account.isLong) { 190 | require(tpPriceUsd >= slPriceUsd, "WrongPrice"); 191 | } else { 192 | require(tpPriceUsd <= slPriceUsd, "WrongPrice"); 193 | } 194 | bytes32 tpOrderKey = _closePosition( 195 | collateralUsd, // collateral.decimals 196 | sizeUsd, // 1e18 197 | tpPriceUsd, // 1e18 198 | 0 // MARKET, TRIGGER 199 | ); 200 | _closeTpslOrderIndexes.add(tpOrderKey); 201 | bytes32 slOrderKey = _closePosition( 202 | collateralUsd, // collateral.decimals 203 | sizeUsd, // 1e18 204 | slPriceUsd, // 1e18 205 | 0 // MARKET, TRIGGER 206 | ); 207 | _closeTpslOrderIndexes.add(slOrderKey); 208 | } else { 209 | _closePosition( 210 | collateralUsd, // collateral.decimals 211 | sizeUsd, // 1e18 212 | priceUsd, // 1e18 213 | flags // MARKET, TRIGGER 214 | ); 215 | } 216 | } 217 | 218 | /// @notice Place a closing request on GMX. 219 | function _closePosition( 220 | uint256 collateralUsd, // collateral.decimals 221 | uint256 sizeUsd, // 1e18 222 | uint96 priceUsd, // 1e18 223 | uint8 flags // MARKET, TRIGGER 224 | ) internal returns (bytes32 orderKey) { 225 | 226 | _updateConfigs(); 227 | _cleanOrders(); 228 | 229 | ClosePositionContext memory context = ClosePositionContext({ 230 | collateralUsd: collateralUsd * GMX_DECIMAL_MULTIPLIER, 231 | sizeUsd: sizeUsd * GMX_DECIMAL_MULTIPLIER, 232 | priceUsd: priceUsd * GMX_DECIMAL_MULTIPLIER, 233 | isMarket: _isMarketOrder(flags), 234 | gmxOrderIndex: 0, 235 | executionFee: 0 236 | }); 237 | return _closePosition(context); 238 | } 239 | 240 | function updateOrder( 241 | bytes32 orderKey, 242 | uint256 collateralDelta, 243 | uint256 sizeDelta, 244 | uint256 triggerPrice, 245 | bool triggerAboveThreshold 246 | ) external onlyTraderOrFactory nonReentrant { 247 | _updateConfigs(); 248 | _cleanOrders(); 249 | 250 | LibGmx.OrderHistory memory history = LibGmx.decodeOrderHistoryKey(orderKey); 251 | if (history.receiver == LibGmx.OrderReceiver.OB_INC) { 252 | IGmxOrderBook(_exchangeConfigs.orderBook).updateIncreaseOrder( 253 | history.index, 254 | sizeDelta * GMX_DECIMAL_MULTIPLIER, 255 | triggerPrice * GMX_DECIMAL_MULTIPLIER, 256 | triggerAboveThreshold 257 | ); 258 | } else if (history.receiver == LibGmx.OrderReceiver.OB_DEC) { 259 | IGmxOrderBook(_exchangeConfigs.orderBook).updateDecreaseOrder( 260 | history.index, 261 | collateralDelta * GMX_DECIMAL_MULTIPLIER, 262 | sizeDelta * GMX_DECIMAL_MULTIPLIER, 263 | triggerPrice * GMX_DECIMAL_MULTIPLIER, 264 | triggerAboveThreshold 265 | ); 266 | } else { 267 | revert("InvalidOrderType"); 268 | } 269 | } 270 | 271 | function withdraw() external nonReentrant { 272 | _updateConfigs(); 273 | _cleanOrders(); 274 | 275 | uint256 ethBalance = address(this).balance; 276 | if (ethBalance > 0) { 277 | if (_account.collateralToken == _WETH) { 278 | IWETH(_WETH).deposit{ value: ethBalance }(); 279 | } else { 280 | AddressUpgradeable.sendValue(payable(_account.account), ethBalance); 281 | emit Withdraw( 282 | _account.collateralToken, 283 | _account.account, 284 | ethBalance 285 | ); 286 | } 287 | } 288 | 289 | uint256 balance = IERC20Upgradeable(_account.collateralToken).balanceOf(address(this)); 290 | //ToDo - should we check if margin is safe? 291 | if (balance > 0) { 292 | _transferToUser(balance); 293 | emit Withdraw( 294 | _account.collateralToken, 295 | _account.account, 296 | balance 297 | ); 298 | } 299 | // clean tpsl orders 300 | _cleanTpslOrders(); 301 | } 302 | 303 | function _transferToUser(uint256 amount) internal { 304 | if (_account.collateralToken == _WETH) { 305 | IWETH(_WETH).withdraw(amount); 306 | Address.sendValue(payable(_account.account), amount); 307 | } else { 308 | IERC20Upgradeable(_account.collateralToken).safeTransfer(_account.account, amount); 309 | } 310 | } 311 | 312 | function cancelOrders(bytes32[] memory keys) external onlyTraderOrFactory nonReentrant { 313 | _cleanOrders(); 314 | _cancelOrders(keys); 315 | } 316 | 317 | function _cancelOrders(bytes32[] memory keys) internal { 318 | for (uint256 i = 0; i < keys.length; i++) { 319 | bool success = _cancelOrder(keys[i]); 320 | require(success, "CancelFailed"); 321 | } 322 | } 323 | 324 | function cancelTimeoutOrders(bytes32[] memory keys) external nonReentrant { 325 | _cleanOrders(); 326 | _cancelTimeoutOrders(keys); 327 | } 328 | 329 | function _cancelTimeoutOrders(bytes32[] memory keys) internal { 330 | uint256 _now = block.timestamp; 331 | uint256 marketTimeout = _exchangeConfigs.marketOrderTimeoutSeconds; 332 | uint256 limitTimeout = _exchangeConfigs.limitOrderTimeoutSeconds; 333 | for (uint256 i = 0; i < keys.length; i++) { 334 | bytes32 orderKey = keys[i]; 335 | LibGmx.OrderHistory memory history = LibGmx.decodeOrderHistoryKey(orderKey); 336 | uint256 elapsed = _now - history.timestamp; 337 | if ( 338 | ((history.receiver == LibGmx.OrderReceiver.PR_INC || history.receiver == LibGmx.OrderReceiver.PR_DEC) && 339 | elapsed >= marketTimeout) || 340 | ((history.receiver == LibGmx.OrderReceiver.OB_INC || history.receiver == LibGmx.OrderReceiver.OB_DEC) && 341 | elapsed >= limitTimeout) 342 | ) { 343 | if (_cancelOrder(orderKey)) { 344 | _cancelTpslOrders(orderKey); 345 | } 346 | } 347 | } 348 | } 349 | 350 | function _cleanOrders() internal { 351 | bytes32[] memory pendingKeys = _pendingOrders.values(); 352 | for (uint256 i = 0; i < pendingKeys.length; i++) { 353 | bytes32 key = pendingKeys[i]; 354 | (bool notExist, ) = LibGmx.getOrder(_exchangeConfigs, key); 355 | if (notExist) { 356 | _removePendingOrder(key); 357 | } 358 | } 359 | } 360 | 361 | function _cleanTpslOrders() internal { 362 | // open tpsl orders 363 | uint256 openLength = _openTpslOrderIndexes.length(); 364 | bytes32[] memory openKeys = new bytes32[](openLength); 365 | for (uint256 i = 0; i < openLength; i++) { 366 | (openKeys[i], ) = _openTpslOrderIndexes.at(i); 367 | } 368 | for (uint256 i = 0; i < openLength; i++) { 369 | // clean all tpsl orders paired with orders that already filled 370 | if (!_pendingOrders.contains(openKeys[i])) { 371 | _cancelTpslOrders(openKeys[i]); 372 | } 373 | } 374 | // close tpsl orders 375 | uint256 closeLength = _closeTpslOrderIndexes.length(); 376 | bytes32[] memory closeKeys = new bytes32[](closeLength); 377 | for (uint256 i = 0; i < closeLength; i++) { 378 | closeKeys[i] = _closeTpslOrderIndexes.at(i); 379 | } 380 | for (uint256 i = 0; i < closeLength; i++) { 381 | // clean all tpsl orders paired with orders that already filled 382 | _cancelOrder(closeKeys[i]); 383 | } 384 | } 385 | } -------------------------------------------------------------------------------- /src/aggregators/gmx/Position.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity 0.8.19; 3 | 4 | import "../../../lib/openzeppelin-contracts-upgradeable/contracts/utils/structs/EnumerableSetUpgradeable.sol"; 5 | import "../../../lib/openzeppelin-contracts-upgradeable/contracts/utils/math/SafeMathUpgradeable.sol"; 6 | import "../../../lib/openzeppelin-contracts-upgradeable/contracts/utils/math/MathUpgradeable.sol"; 7 | 8 | import "../lib/LibUtils.sol"; 9 | import "./lib/LibGmx.sol"; 10 | import "./Storage.sol"; 11 | 12 | contract Position is Storage{ 13 | using LibUtils for uint256; 14 | using MathUpgradeable for uint256; 15 | using SafeMathUpgradeable for uint256; 16 | using EnumerableSetUpgradeable for EnumerableSetUpgradeable.Bytes32Set; 17 | using EnumerableMapUpgradeable for EnumerableMapUpgradeable.Bytes32ToBytes32Map; 18 | 19 | uint256 internal constant GMX_DECIMAL_MULTIPLIER = 1e12; // 30 - 18 20 | uint256 internal constant MAX_PENDING_ORDERS = 64; 21 | 22 | event AddPendingOrder( 23 | LibGmx.OrderCategory category, 24 | LibGmx.OrderReceiver receiver, 25 | uint256 index, 26 | uint256 timestamp 27 | ); 28 | event RemovePendingOrder(bytes32 key); 29 | event CancelOrder(bytes32 key, bool success); 30 | 31 | event OpenPosition(address collateralToken, address indexToken, bool isLong, OpenPositionContext context); 32 | event ClosePosition(address collateralToken, address indexToken, bool isLong, ClosePositionContext context); 33 | 34 | function _hasPendingOrder(bytes32 key) internal view returns (bool) { 35 | return _pendingOrders.contains(key); 36 | } 37 | 38 | function _getPendingOrders() internal view returns (bytes32[] memory) { 39 | return _pendingOrders.values(); 40 | } 41 | 42 | function _removePendingOrder(bytes32 key) internal { 43 | _pendingOrders.remove(key); 44 | emit RemovePendingOrder(key); 45 | } 46 | 47 | function _getGmxPosition() internal view returns (IGmxVault.Position memory) { 48 | return IGmxVault(_exchangeConfigs.vault).positions(_gmxPositionKey); 49 | } 50 | 51 | function _addPendingOrder( 52 | LibGmx.OrderCategory category, 53 | LibGmx.OrderReceiver receiver 54 | ) internal returns (uint256 index, bytes32 orderKey) { 55 | index = LibGmx.getOrderIndex(_exchangeConfigs, receiver); 56 | orderKey = LibGmx.encodeOrderHistoryKey(category, receiver, index, block.timestamp); 57 | require(_pendingOrders.add(orderKey), "AddFailed"); 58 | emit AddPendingOrder(category, receiver, index, block.timestamp); 59 | } 60 | 61 | function _getMarginValue( 62 | IGmxVault.Position memory position, 63 | uint256 deltaCollateral, 64 | uint256 priceUsd 65 | ) internal view returns (uint256 accountValue, bool isNegative) { 66 | bool hasProfit = false; 67 | uint256 gmxPnlUsd = 0; 68 | uint256 gmxFundingFeeUsd = 0; 69 | // 1. gmx pnl and funding, 1e30 70 | if (position.sizeUsd != 0) { 71 | (hasProfit, gmxPnlUsd) = LibGmx.getPnl( 72 | _exchangeConfigs, 73 | _account.indexToken, 74 | position.sizeUsd, 75 | position.averagePrice, 76 | _account.isLong, 77 | priceUsd, 78 | position.lastIncreasedTime 79 | ); 80 | gmxFundingFeeUsd = IGmxVault(_exchangeConfigs.vault).getFundingFee( 81 | _account.collateralToken, 82 | position.sizeUsd, 83 | position.entryFundingRate 84 | ); 85 | } 86 | //ToDo - need to double check the below formulae to make sure they are solid 87 | int256 value = int256(position.collateralUsd) + 88 | (hasProfit ? int256(gmxPnlUsd) : -int256(gmxPnlUsd)) - 89 | int256(gmxFundingFeeUsd); 90 | if (_account.isLong) { 91 | value += (int256(deltaCollateral) * int256(position.averagePrice)) / int256(10**_account.collateralDecimals); // 1e30 92 | } else { 93 | uint256 tokenPrice = LibGmx.getOraclePrice(_exchangeConfigs, _account.collateralToken, false); // 1e30 94 | value += (int256(deltaCollateral) * int256(tokenPrice)) / int256(10**_account.collateralDecimals); // 1e30 95 | } 96 | if (value > 0) { 97 | accountValue = uint256(value); 98 | isNegative = false; 99 | } else { 100 | accountValue = uint256(-value); 101 | isNegative = true; 102 | } 103 | } 104 | 105 | function _isMarginSafe( 106 | IGmxVault.Position memory position, 107 | uint256 deltaCollateralUsd, 108 | uint256 deltaSizeUsd, 109 | uint256 priceUsd, 110 | uint32 threshold 111 | ) internal view returns (bool) { 112 | if (position.sizeUsd == 0) { 113 | return true; 114 | } 115 | (uint256 accountValue, bool isNegative) = _getMarginValue(position, deltaCollateralUsd, priceUsd); // 1e30 116 | if (isNegative) { 117 | return false; 118 | } 119 | uint256 liquidationFeeUsd = IGmxVault(_exchangeConfigs.vault).liquidationFeeUsd(); 120 | return accountValue >= (position.sizeUsd + deltaSizeUsd).rate(threshold).max(liquidationFeeUsd); 121 | } 122 | 123 | function _openPosition(OpenPositionContext memory context) internal returns(bytes32 orderKey){ 124 | require(_pendingOrders.length() <= MAX_PENDING_ORDERS, "TooManyPendingOrders"); 125 | IGmxVault.Position memory position = _getGmxPosition(); 126 | require( 127 | _isMarginSafe( 128 | position, 129 | context.amountIn, 130 | context.sizeUsd, 131 | LibGmx.getOraclePrice(_exchangeConfigs, _account.indexToken, !_account.isLong), 132 | _exchangeConfigs.initialMarginRate 133 | ), 134 | "ImMarginUnsafe" 135 | ); 136 | address[] memory path = new address[](1); 137 | path[0] = _account.collateralToken; 138 | if (context.isMarket) { 139 | context.executionFee = LibGmx.getPrExecutionFee(_exchangeConfigs); 140 | IGmxPositionRouter(_exchangeConfigs.positionRouter).createIncreasePosition{ value: context.executionFee }( 141 | path, 142 | _account.indexToken, 143 | context.amountIn, 144 | 0, 145 | context.sizeUsd, 146 | _account.isLong, 147 | _account.isLong ? type(uint256).max : 0, 148 | context.executionFee, 149 | _exchangeConfigs.referralCode, 150 | address(0) 151 | ); 152 | (context.gmxOrderIndex, orderKey) = _addPendingOrder( 153 | LibGmx.OrderCategory.OPEN, 154 | LibGmx.OrderReceiver.PR_INC 155 | ); 156 | } else { 157 | context.executionFee = LibGmx.getObExecutionFee(_exchangeConfigs); 158 | IGmxOrderBook(_exchangeConfigs.orderBook).createIncreaseOrder{ value: context.executionFee }( 159 | path, 160 | context.amountIn, 161 | _account.indexToken, 162 | 0, 163 | context.sizeUsd, 164 | _account.collateralToken, 165 | _account.isLong, 166 | context.priceUsd, 167 | !_account.isLong, 168 | context.executionFee, 169 | false 170 | ); 171 | (context.gmxOrderIndex, orderKey) = _addPendingOrder( 172 | LibGmx.OrderCategory.OPEN, 173 | LibGmx.OrderReceiver.OB_INC 174 | ); 175 | } 176 | emit OpenPosition(_account.collateralToken, _account.indexToken, _account.isLong, context); 177 | } 178 | 179 | function _closePosition(ClosePositionContext memory context) internal returns(bytes32 orderKey){ 180 | require(_pendingOrders.length() <= MAX_PENDING_ORDERS * 2, "TooManyPendingOrders"); 181 | 182 | IGmxVault.Position memory position = _getGmxPosition(); 183 | require( 184 | _isMarginSafe( 185 | position, 186 | 0, 187 | 0, 188 | LibGmx.getOraclePrice(_exchangeConfigs, _account.indexToken, !_account.isLong), 189 | _exchangeConfigs.maintenanceMarginRate 190 | ), 191 | "MmMarginUnsafe" 192 | ); 193 | 194 | address[] memory path = new address[](1); 195 | path[0] = _account.collateralToken; 196 | if (context.isMarket) { 197 | context.executionFee = LibGmx.getPrExecutionFee(_exchangeConfigs); 198 | context.priceUsd = _account.isLong ? 0 : type(uint256).max; 199 | IGmxPositionRouter(_exchangeConfigs.positionRouter).createDecreasePosition{ value: context.executionFee }( 200 | path, // no swap for collateral 201 | _account.indexToken, 202 | context.collateralUsd, 203 | context.sizeUsd, 204 | _account.isLong, // no swap for collateral 205 | address(this), 206 | context.priceUsd, 207 | 0, 208 | context.executionFee, 209 | false, 210 | address(0) 211 | ); 212 | (context.gmxOrderIndex, orderKey) = _addPendingOrder(LibGmx.OrderCategory.CLOSE, LibGmx.OrderReceiver.PR_DEC); 213 | } else { 214 | context.executionFee = LibGmx.getObExecutionFee(_exchangeConfigs); 215 | uint256 oralcePrice = LibGmx.getOraclePrice(_exchangeConfigs, _account.indexToken, !_account.isLong); 216 | uint256 priceUsd = context.priceUsd; 217 | IGmxOrderBook(_exchangeConfigs.orderBook).createDecreaseOrder{ value: context.executionFee }( 218 | _account.indexToken, 219 | context.sizeUsd, 220 | _account.collateralToken, 221 | context.collateralUsd, 222 | _account.isLong, 223 | priceUsd, 224 | priceUsd >= oralcePrice 225 | ); 226 | (context.gmxOrderIndex, orderKey) = _addPendingOrder(LibGmx.OrderCategory.CLOSE, LibGmx.OrderReceiver.OB_DEC); 227 | } 228 | emit ClosePosition(_account.collateralToken, _account.indexToken, _account.isLong, context); 229 | } 230 | 231 | function _cancelOrder(bytes32 key) internal returns (bool success) { 232 | if (!_hasPendingOrder(key)){ 233 | success = true; 234 | } else { 235 | success = LibGmx.cancelOrder(_exchangeConfigs, key); 236 | if(success) { 237 | _removePendingOrder(key); 238 | emit CancelOrder(key, success); 239 | } 240 | } 241 | } 242 | 243 | function _cancelTpslOrders(bytes32 orderKey) internal returns (bool success) { 244 | (bool exists, bytes32 tpslIndex) = _openTpslOrderIndexes.tryGet(orderKey); 245 | if (!exists) { 246 | success = true; 247 | } else { 248 | (bytes32 tpOrderKey, bytes32 slOrderKey) = LibGmx.decodeTpslIndex(orderKey, tpslIndex); 249 | if (_cancelOrder(tpOrderKey) && _cancelOrder(slOrderKey)) { 250 | _openTpslOrderIndexes.remove(orderKey); 251 | success = true; 252 | } 253 | } 254 | } 255 | } -------------------------------------------------------------------------------- /src/aggregators/gmx/Storage.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity 0.8.19; 3 | 4 | import "../../../lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol"; 5 | import "../../../lib/openzeppelin-contracts-upgradeable/contracts/utils/structs/EnumerableSetUpgradeable.sol"; 6 | import "../../../lib/openzeppelin-contracts-upgradeable/contracts/utils/structs/EnumerableMapUpgradeable.sol"; 7 | 8 | import "./Types.sol"; 9 | 10 | contract Storage is Initializable { 11 | uint256 internal constant EXCHANGE_ID = 1; 12 | 13 | uint32 internal _localexchangeVersion; 14 | mapping(address => uint32) _localAssetVersions; 15 | 16 | address internal _factory; 17 | bytes32 internal _gmxPositionKey; 18 | 19 | ExchangeConfigs internal _exchangeConfigs; 20 | 21 | AccountState internal _account; 22 | EnumerableSetUpgradeable.Bytes32Set internal _pendingOrders; 23 | EnumerableMapUpgradeable.Bytes32ToBytes32Map internal _openTpslOrderIndexes; 24 | EnumerableSetUpgradeable.Bytes32Set internal _closeTpslOrderIndexes; 25 | 26 | 27 | //ToDo - Do we need these gaps? 28 | //bytes32[50] private __gaps; 29 | 30 | //Position Market order constant flag 31 | uint8 constant POSITION_MARKET_ORDER = 0x40; 32 | uint8 constant POSITION_TPSL_ORDER = 0x08; 33 | } -------------------------------------------------------------------------------- /src/aggregators/gmx/Types.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity 0.8.19; 3 | 4 | enum ExchangeConfigIds { 5 | VAULT, 6 | POSITION_ROUTER, 7 | ORDER_BOOK, 8 | ROUTER, 9 | REFERRAL_CODE, 10 | MARKET_ORDER_TIMEOUT_SECONDS, 11 | LIMIT_ORDER_TIMEOUT_SECONDS, 12 | INITIAL_MARGIN_RATE, 13 | MAINTENANCE_MARGIN_RATE, 14 | END 15 | } 16 | 17 | struct ExchangeConfigs { 18 | address vault; 19 | address positionRouter; 20 | address orderBook; 21 | address router; 22 | bytes32 referralCode; 23 | // ======================== 24 | uint32 marketOrderTimeoutSeconds; 25 | uint32 limitOrderTimeoutSeconds; 26 | uint32 initialMarginRate; 27 | uint32 maintenanceMarginRate; 28 | bytes32[20] reserved; 29 | } 30 | 31 | struct AccountState { 32 | address account; 33 | address collateralToken; 34 | address indexToken; // 160 35 | bool isLong; // 8 36 | uint8 collateralDecimals; 37 | bytes32[20] reserved; 38 | } 39 | 40 | struct OpenPositionContext { 41 | // parameters 42 | uint256 amountIn; 43 | uint256 sizeUsd; 44 | uint256 priceUsd; 45 | bool isMarket; 46 | // calculated 47 | uint256 fee; 48 | uint256 amountOut; 49 | uint256 gmxOrderIndex; 50 | uint256 executionFee; 51 | } 52 | 53 | struct ClosePositionContext { 54 | uint256 collateralUsd; 55 | uint256 sizeUsd; 56 | uint256 priceUsd; 57 | bool isMarket; 58 | uint256 gmxOrderIndex; 59 | uint256 executionFee; 60 | } -------------------------------------------------------------------------------- /src/aggregators/gmx/lib/LibGmx.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity 0.8.19; 3 | 4 | import "../../../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; 5 | import "../../../../lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol"; 6 | 7 | import "../../../interfaces/IGmxOrderBook.sol"; 8 | import "../../../interfaces/IGmxPositionRouter.sol"; 9 | import "../../../interfaces/IGmxVault.sol"; 10 | 11 | import "../Types.sol"; 12 | 13 | library LibGmx { 14 | using SafeERC20 for IERC20; 15 | 16 | enum OrderCategory { 17 | NONE, 18 | OPEN, 19 | CLOSE 20 | } 21 | 22 | enum OrderReceiver { 23 | PR_INC, 24 | PR_DEC, 25 | OB_INC, 26 | OB_DEC 27 | } 28 | 29 | struct OrderHistory { 30 | OrderCategory category; // 4 31 | OrderReceiver receiver; // 4 32 | uint64 index; // 64 33 | uint96 zero; // 96 34 | uint88 timestamp; // 80 35 | } 36 | 37 | function getOraclePrice( 38 | ExchangeConfigs storage exchangeConfigs, 39 | address token, 40 | bool useMaxPrice 41 | ) internal view returns (uint256 price) { 42 | // open long = max 43 | // open short = min 44 | // close long = min 45 | // close short = max 46 | price = useMaxPrice //isOpen == isLong 47 | ? IGmxVault(exchangeConfigs.vault).getMaxPrice(token) 48 | : IGmxVault(exchangeConfigs.vault).getMinPrice(token); 49 | require(price != 0, "ZeroOraclePrice"); 50 | } 51 | 52 | function swap( 53 | ExchangeConfigs memory exchangeConfigs, 54 | address tokenIn, 55 | address tokenOut, 56 | uint256 amountIn, 57 | uint256 minOut 58 | ) internal returns (uint256 amountOut) { 59 | IERC20(tokenIn).safeTransfer(exchangeConfigs.vault, amountIn); 60 | amountOut = IGmxVault(exchangeConfigs.vault).swap(tokenIn, tokenOut, address(this)); 61 | require(amountOut >= minOut, "AmountOutNotReached"); 62 | } 63 | 64 | function getOrderIndex(ExchangeConfigs memory exchangeConfigs, OrderReceiver receiver) 65 | internal 66 | view 67 | returns (uint256 index) 68 | { 69 | if (receiver == OrderReceiver.PR_INC) { 70 | index = IGmxPositionRouter(exchangeConfigs.positionRouter).increasePositionsIndex(address(this)); 71 | } else if (receiver == OrderReceiver.PR_DEC) { 72 | index = IGmxPositionRouter(exchangeConfigs.positionRouter).decreasePositionsIndex(address(this)); 73 | } else if (receiver == OrderReceiver.OB_INC) { 74 | index = IGmxOrderBook(exchangeConfigs.orderBook).increaseOrdersIndex(address(this)) - 1; 75 | } else if (receiver == OrderReceiver.OB_DEC) { 76 | index = IGmxOrderBook(exchangeConfigs.orderBook).decreaseOrdersIndex(address(this)) - 1; 77 | } 78 | } 79 | 80 | function getOrder(ExchangeConfigs memory exchangeConfigs, bytes32 key) 81 | internal 82 | view 83 | returns (bool isFilled, OrderHistory memory history) 84 | { 85 | history = decodeOrderHistoryKey(key); 86 | if (history.receiver == OrderReceiver.PR_INC) { 87 | IGmxPositionRouter.IncreasePositionRequest memory request = IGmxPositionRouter( 88 | exchangeConfigs.positionRouter 89 | ).increasePositionRequests(encodeOrderKey(address(this), history.index)); 90 | isFilled = request.account == address(0); 91 | } else if (history.receiver == OrderReceiver.PR_DEC) { 92 | IGmxPositionRouter.DecreasePositionRequest memory request = IGmxPositionRouter( 93 | exchangeConfigs.positionRouter 94 | ).decreasePositionRequests(encodeOrderKey(address(this), history.index)); 95 | isFilled = request.account == address(0); 96 | } else if (history.receiver == OrderReceiver.OB_INC) { 97 | (address collateralToken, , , , , , , , ) = IGmxOrderBook(exchangeConfigs.orderBook).getIncreaseOrder( 98 | address(this), 99 | history.index 100 | ); 101 | isFilled = collateralToken == address(0); 102 | } else if (history.receiver == OrderReceiver.OB_DEC) { 103 | (address collateralToken, , , , , , , ) = IGmxOrderBook(exchangeConfigs.orderBook).getDecreaseOrder( 104 | address(this), 105 | history.index 106 | ); 107 | isFilled = collateralToken == address(0); 108 | } else { 109 | revert(); 110 | } 111 | } 112 | 113 | function cancelOrderFromPositionRouter(address positionRouter, bytes32 key) internal returns (bool success) { 114 | OrderHistory memory history = decodeOrderHistoryKey(key); 115 | success = false; 116 | if (history.receiver == OrderReceiver.PR_INC) { 117 | try 118 | IGmxPositionRouter(positionRouter).cancelIncreasePosition( 119 | encodeOrderKey(address(this), history.index), 120 | payable(address(this)) 121 | ) 122 | returns (bool _success) { 123 | success = _success; 124 | } catch {} 125 | } else if (history.receiver == OrderReceiver.PR_DEC) { 126 | try 127 | IGmxPositionRouter(positionRouter).cancelDecreasePosition( 128 | encodeOrderKey(address(this), history.index), 129 | payable(address(this)) 130 | ) 131 | returns (bool _success) { 132 | success = _success; 133 | } catch {} 134 | } 135 | } 136 | 137 | function cancelOrderFromOrderBook(address orderBook, bytes32 key) internal returns (bool success) { 138 | OrderHistory memory history = decodeOrderHistoryKey(key); 139 | success = false; 140 | if (history.receiver == OrderReceiver.OB_INC) { 141 | try IGmxOrderBook(orderBook).cancelIncreaseOrder(history.index) { 142 | success = true; 143 | } catch {} 144 | } else if (history.receiver == OrderReceiver.OB_DEC) { 145 | try IGmxOrderBook(orderBook).cancelDecreaseOrder(history.index) { 146 | success = true; 147 | } catch {} 148 | } 149 | } 150 | 151 | function cancelOrder(ExchangeConfigs memory exchangeConfigs, bytes32 key) internal returns (bool success) { 152 | OrderHistory memory history = decodeOrderHistoryKey(key); 153 | success = false; 154 | if (history.receiver == OrderReceiver.PR_INC) { 155 | try 156 | IGmxPositionRouter(exchangeConfigs.positionRouter).cancelIncreasePosition( 157 | encodeOrderKey(address(this), history.index), 158 | payable(address(this)) 159 | ) 160 | returns (bool _success) { 161 | success = _success; 162 | } catch {} 163 | } else if (history.receiver == OrderReceiver.PR_DEC) { 164 | try 165 | IGmxPositionRouter(exchangeConfigs.positionRouter).cancelDecreasePosition( 166 | encodeOrderKey(address(this), history.index), 167 | payable(address(this)) 168 | ) 169 | returns (bool _success) { 170 | success = _success; 171 | } catch {} 172 | } else if (history.receiver == OrderReceiver.OB_INC) { 173 | try IGmxOrderBook(exchangeConfigs.orderBook).cancelIncreaseOrder(history.index) { 174 | success = true; 175 | } catch {} 176 | } else if (history.receiver == OrderReceiver.OB_DEC) { 177 | try IGmxOrderBook(exchangeConfigs.orderBook).cancelDecreaseOrder(history.index) { 178 | success = true; 179 | } catch {} 180 | } else { 181 | revert(); 182 | } 183 | } 184 | 185 | function getPnl( 186 | ExchangeConfigs memory exchangeConfigs, 187 | address indexToken, 188 | uint256 size, 189 | uint256 averagePriceUsd, 190 | bool isLong, 191 | uint256 priceUsd, 192 | uint256 lastIncreasedTime 193 | ) internal view returns (bool, uint256) { 194 | require(priceUsd > 0, ""); 195 | uint256 priceDelta = averagePriceUsd > priceUsd ? averagePriceUsd - priceUsd : priceUsd - averagePriceUsd; 196 | uint256 delta = (size * priceDelta) / averagePriceUsd; 197 | bool hasProfit; 198 | if (isLong) { 199 | hasProfit = priceUsd > averagePriceUsd; 200 | } else { 201 | hasProfit = averagePriceUsd > priceUsd; 202 | } 203 | uint256 minProfitTime = IGmxVault(exchangeConfigs.vault).minProfitTime(); 204 | uint256 minProfitBasisPoints = IGmxVault(exchangeConfigs.vault).minProfitBasisPoints(indexToken); 205 | uint256 minBps = block.timestamp > lastIncreasedTime + minProfitTime ? 0 : minProfitBasisPoints; 206 | if (hasProfit && delta * 10000 <= size * minBps) { 207 | delta = 0; 208 | } 209 | return (hasProfit, delta); 210 | } 211 | 212 | function encodeOrderKey(address account, uint256 index) internal pure returns (bytes32) { 213 | return keccak256(abi.encodePacked(account, index)); 214 | } 215 | 216 | function decodeOrderHistoryKey(bytes32 key) internal pure returns (OrderHistory memory history) { 217 | // 252 248 184 88 0 218 | // +------------+------------+------------------+-----------+-----------+ 219 | // | category 4 | receiver 4 | gmxOrderIndex 64 | 0 96 | time 88 | 220 | // +------------+------------+------------------+-----------+-----------+ 221 | history.category = OrderCategory(uint8(bytes1(key)) >> 4); 222 | history.receiver = OrderReceiver(uint8(bytes1(key)) & 0x0f); 223 | history.index = uint64(bytes8(key << 8)); 224 | history.zero = uint96(uint256(key >> 88)); 225 | history.timestamp = uint88(uint256(key)); 226 | } 227 | 228 | function encodeOrderHistoryKey( 229 | OrderCategory category, 230 | OrderReceiver receiver, 231 | uint256 index, 232 | uint256 timestamp 233 | ) internal pure returns (bytes32 data) { 234 | // 252 248 184 88 0 235 | // +------------+------------+------------------+-----------+-----------+ 236 | // | category 4 | receiver 4 | gmxOrderIndex 64 | 0 96 | time 88 | 237 | // +------------+------------+------------------+-----------+-----------+ 238 | require(index < type(uint64).max, "GmxOrderIndexOverflow"); 239 | require(timestamp < type(uint88).max, "FeeOverflow"); 240 | data = 241 | bytes32(uint256(category) << 252) | // 256 - 4 242 | bytes32(uint256(receiver) << 248) | // 256 - 4 - 4 243 | bytes32(uint256(index) << 184) | // 256 - 4 - 4 - 64 244 | bytes32(uint256(0) << 88) | // 256 - 4 - 4 - 64 - 96 245 | bytes32(uint256(timestamp)); 246 | } 247 | 248 | function encodeTpslIndex(bytes32 tpOrderKey, bytes32 slOrderKey) internal pure returns (bytes32) { 249 | // 252 248 184 250 | // +------------+------------+------------------+ 251 | // | category 4 | receiver 4 | gmxOrderIndex 64 | 252 | // +------------+------------+------------------+ 253 | // store head of orderkey without timestamp, since tpsl orders should have the same timestamp as the open order. 254 | return bytes32(bytes9(tpOrderKey)) | (bytes32(bytes9(slOrderKey)) >> 128); 255 | } 256 | 257 | function decodeTpslIndex( 258 | bytes32 orderKey, 259 | bytes32 tpslIndex 260 | ) internal pure returns (bytes32 tpOrderKey, bytes32 slOrderKey) { 261 | bytes32 timestamp = bytes32(uint256(uint88(uint256(orderKey)))); 262 | // timestamp of all tpsl orders (main +tp +sl) are same 263 | tpOrderKey = bytes32(bytes16(tpslIndex)); 264 | tpOrderKey = tpOrderKey != bytes32(0) ? tpOrderKey | timestamp : tpOrderKey; 265 | slOrderKey = (tpslIndex << 128); 266 | slOrderKey = slOrderKey != bytes32(0) ? slOrderKey | timestamp : slOrderKey; 267 | } 268 | 269 | function getPrExecutionFee(ExchangeConfigs memory exchangeConfigs) internal view returns (uint256) { 270 | return IGmxPositionRouter(exchangeConfigs.positionRouter).minExecutionFee(); 271 | } 272 | 273 | function getObExecutionFee(ExchangeConfigs memory exchangeConfigs) internal view returns (uint256) { 274 | return IGmxOrderBook(exchangeConfigs.orderBook).minExecutionFee() + 1; 275 | } 276 | } -------------------------------------------------------------------------------- /src/aggregators/lib/LibMath.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity 0.8.19; 3 | 4 | library LibMath { 5 | function min(uint96 a, uint96 b) internal pure returns (uint96) { 6 | return a <= b ? a : b; 7 | } 8 | 9 | function min32(uint32 a, uint32 b) internal pure returns (uint32) { 10 | return a <= b ? a : b; 11 | } 12 | 13 | function max32(uint32 a, uint32 b) internal pure returns (uint32) { 14 | return a >= b ? a : b; 15 | } 16 | 17 | function wmul(uint256 a, uint256 b) internal pure returns (uint256) { 18 | return (a * b) / 1e18; 19 | } 20 | 21 | function rmul(uint256 a, uint256 b) internal pure returns (uint256) { 22 | return (a * b) / 1e5; 23 | } 24 | 25 | function wdiv(uint256 a, uint256 b) internal pure returns (uint256) { 26 | return (a * 1e18) / b; 27 | } 28 | 29 | function safeUint32(uint256 n) internal pure returns (uint32) { 30 | require(n <= type(uint32).max, "O32"); // uint32 Overflow 31 | return uint32(n); 32 | } 33 | 34 | function safeUint96(uint256 n) internal pure returns (uint96) { 35 | require(n <= type(uint96).max, "O96"); // uint96 Overflow 36 | return uint96(n); 37 | } 38 | 39 | function safeUint128(uint256 n) internal pure returns (uint128) { 40 | require(n <= type(uint128).max, "O12"); // uint128 Overflow 41 | return uint128(n); 42 | } 43 | } -------------------------------------------------------------------------------- /src/aggregators/lib/LibUtils.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity 0.8.19; 3 | 4 | library LibUtils { 5 | uint256 internal constant RATE_DENOMINATOR = 1e5; 6 | 7 | function toAddress(bytes32 value) internal pure returns (address) { 8 | return address(bytes20(value)); 9 | } 10 | 11 | function toAddress(uint256 value) internal pure returns (address) { 12 | return address(bytes20(bytes32(value))); 13 | } 14 | 15 | function toU256(address value) internal pure returns (uint256) { 16 | return uint256(uint160(value)); 17 | } 18 | 19 | function toU32(bytes32 value) internal pure returns (uint32) { 20 | require(uint256(value) <= type(uint32).max, "OU32"); 21 | return uint32(uint256(value)); 22 | } 23 | 24 | function toU32(uint256 value) internal pure returns (uint32) { 25 | require(value <= type(uint32).max, "OU32"); 26 | return uint32(value); 27 | } 28 | 29 | function toU8(uint256 value) internal pure returns (uint8) { 30 | require(value <= type(uint8).max, "OU8"); 31 | return uint8(value); 32 | } 33 | 34 | function toU96(uint256 n) internal pure returns (uint96) { 35 | require(n <= type(uint96).max, "OU96"); // uint96 Overflow 36 | return uint96(n); 37 | } 38 | 39 | function rate(uint256 value, uint32 rate_) internal pure returns (uint256) { 40 | return (value * rate_) / RATE_DENOMINATOR; 41 | } 42 | } -------------------------------------------------------------------------------- /src/aggregators/mux/Config.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity 0.8.19; 3 | 4 | import "../../../lib/openzeppelin-contracts-upgradeable/contracts/token/ERC20/extensions/IERC20MetadataUpgradeable.sol"; 5 | 6 | import "../../interfaces/IMuxProxyFactory.sol"; 7 | 8 | import "../lib/LibUtils.sol"; 9 | import "./lib/LibMux.sol"; 10 | import "./Storage.sol"; 11 | import "./Position.sol"; 12 | 13 | contract Config is Storage, Position{ 14 | using LibUtils for bytes32; 15 | using LibUtils for address; 16 | using LibUtils for uint256; 17 | 18 | function _updateConfigs() internal virtual{ 19 | (uint32 latestexchangeVersion) = IMuxProxyFactory(_factory).getConfigVersions( 20 | EXCHANGE_ID 21 | ); 22 | if (_localexchangeVersion < latestexchangeVersion) { 23 | _updateexchangeConfigs(); 24 | _localexchangeVersion = latestexchangeVersion; 25 | } 26 | } 27 | 28 | function _updateexchangeConfigs() internal { 29 | uint256[] memory values = IMuxProxyFactory(_factory).getExchangeConfig(EXCHANGE_ID); 30 | require(values.length >= uint256(ExchangeConfigIds.END), "MissingConfigs"); 31 | 32 | address newLiquidityPool = values[uint256(ExchangeConfigIds.LIQUIDITY_POOL)].toAddress(); 33 | address newOrderBook = values[uint256(ExchangeConfigIds.ORDER_BOOK)].toAddress(); 34 | 35 | //ToDo - is cancelling orders when we change orderBook really necessary? 36 | _onMuxOrderUpdated(_exchangeConfigs.orderBook, newOrderBook); 37 | 38 | _exchangeConfigs.liquidityPool = newLiquidityPool; 39 | _exchangeConfigs.orderBook = newOrderBook; 40 | _exchangeConfigs.referralCode = bytes32(values[uint256(ExchangeConfigIds.REFERRAL_CODE)]); 41 | } 42 | 43 | function _onMuxOrderUpdated(address previousOrderBook, address newOrderBook) internal{ 44 | bool cancelOrderBook = previousOrderBook != newOrderBook; 45 | uint64[] memory pendingKeys = _pendingOrders; 46 | for (uint256 i = 0; i < pendingKeys.length; i++) { 47 | uint64 key = pendingKeys[i]; 48 | if (cancelOrderBook) { 49 | LibMux.cancelOrderFromOrderBook(newOrderBook, key); 50 | _removePendingOrder(key); 51 | } 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /src/aggregators/mux/MuxAdapter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity 0.8.19; 3 | 4 | import "../../../lib/openzeppelin-contracts-upgradeable/contracts/token/ERC20/extensions/IERC20MetadataUpgradeable.sol"; 5 | import "../../../lib/openzeppelin-contracts-upgradeable/contracts/token/ERC20/utils/SafeERC20Upgradeable.sol"; 6 | import "../../../lib/openzeppelin-contracts-upgradeable/contracts/security/ReentrancyGuardUpgradeable.sol"; 7 | import "../../../lib/openzeppelin-contracts/contracts/utils/Address.sol"; 8 | 9 | import "../../interfaces/IWETH.sol"; 10 | import "../../interfaces/IMuxOrderBook.sol"; 11 | 12 | import "../../components/ImplementationGuard.sol"; 13 | import "./Storage.sol"; 14 | import "./Config.sol"; 15 | 16 | contract MuxAdapter is Storage, Config, ImplementationGuard, ReentrancyGuardUpgradeable{ 17 | using SafeERC20Upgradeable for IERC20Upgradeable; 18 | using Address for address; 19 | 20 | address internal immutable _WETH; 21 | 22 | event Withdraw( 23 | address collateralAddress, 24 | address account, 25 | uint256 balance 26 | ); 27 | 28 | constructor(address weth) ImplementationGuard() { 29 | _WETH = weth; 30 | } 31 | 32 | receive() external payable {} 33 | 34 | modifier onlyTraderOrFactory() { 35 | require(msg.sender == _account.account || msg.sender == _factory, "OnlyTraderOrFactory"); 36 | _; 37 | } 38 | 39 | modifier onlyFactory() { 40 | require(msg.sender == _factory, "onlyFactory"); 41 | _; 42 | } 43 | 44 | function initialize( 45 | uint256 exchangeId, 46 | address account, 47 | address collateralToken, 48 | uint8 collateralId, 49 | uint8 assetId, 50 | bool isLong 51 | ) external initializer onlyDelegateCall { 52 | require(exchangeId == EXCHANGE_ID, "Invalidexchange"); 53 | 54 | _factory = msg.sender; 55 | 56 | _account.account = account; 57 | _account.collateralToken = collateralToken; 58 | _account.collateralId = collateralId; 59 | _account.indexId = assetId; 60 | _account.isLong = isLong; 61 | //We set the profitTokenAdderss to collateralToken during initialization, if the user provides with a profitTokenAddress during closing Position, we will update this value. 62 | _account.profitTokenAddress = collateralToken; 63 | _account.collateralDecimals = IERC20MetadataUpgradeable(collateralToken).decimals(); 64 | _updateConfigs(); 65 | _subAccountId = encodeSubAccountId(isLong); 66 | } 67 | 68 | function accountState() external view returns(AccountState memory){ 69 | return _account; 70 | } 71 | 72 | function getSubAccountId() external view returns(bytes32){ 73 | return _subAccountId; 74 | } 75 | 76 | function encodeSubAccountId(bool isLong) internal view returns (bytes32) 77 | { 78 | return bytes32( 79 | (uint256(uint160(address(this))) << 96) | 80 | (uint256(_account.collateralId) << 88) | 81 | (uint256(_account.indexId) << 80) | 82 | (uint256(isLong ? 1 : 0) << 72) 83 | ); 84 | } 85 | 86 | /// @notice Place a openning request on MUX. 87 | function placePositionOrder( 88 | uint96 collateralAmount, // tokenIn.decimals 89 | uint96 size, // 1e18 90 | uint96 price, // 1e18 91 | uint8 flags, // MARKET, TRIGGER 92 | uint96 assetPrice, // 1e18 93 | uint96 collateralPrice, // 1e18 94 | uint32 deadline, 95 | bool isLong, 96 | uint8 profitTokenId, 97 | address profitTokenAddress, 98 | PositionOrderExtra memory extra 99 | ) external payable onlyTraderOrFactory nonReentrant { 100 | 101 | _updateConfigs(); 102 | _cleanOrders(); 103 | 104 | uint32 positionDeadline; 105 | uint96 positionPrice; 106 | 107 | //For a market order, if the deadline and price are not zero, transaction will fail on MUX side 108 | if((flags & POSITION_MARKET_ORDER) != 0){ 109 | positionDeadline = 0; 110 | positionPrice = 0; 111 | }else{ 112 | positionDeadline = deadline; 113 | positionPrice = price; 114 | } 115 | 116 | //For an open order, if the profitTokenId is not zero, transaction will fail on MUX side 117 | if((flags & POSITION_OPEN) != 0){ 118 | //We will not have to deposit or give the approvals for close position 119 | if (_account.collateralToken == _WETH) { 120 | require(msg.value >= collateralAmount, "Insufficient ETH"); 121 | } else{ 122 | require(msg.value == 0, "Unnecessary ETH"); 123 | IERC20Upgradeable(_account.collateralToken).approve(_exchangeConfigs.orderBook, collateralAmount); 124 | } 125 | } else{ 126 | //For close position, we will save the profit token at 127 | _account.profitTokenAddress = profitTokenAddress; 128 | } 129 | 130 | PositionContext memory context = PositionContext({ 131 | collateralAmount : collateralAmount, 132 | size : size, 133 | price : positionPrice, 134 | flags : flags, 135 | assetPrice : assetPrice, 136 | collateralPrice : collateralPrice, 137 | profitTokenId : profitTokenId, 138 | subAccountId : _subAccountId, 139 | deadline : positionDeadline, 140 | isLong : isLong, 141 | extra : extra 142 | }); 143 | 144 | _placePositionOrder(context); 145 | } 146 | 147 | function cancelOrders(uint64[] memory orderIds) external onlyTraderOrFactory nonReentrant{ 148 | for (uint256 i = 0; i < orderIds.length; i++) { 149 | bool success = _cancelOrder(orderIds[i]); 150 | require(success, "CancelFailed"); 151 | } 152 | } 153 | 154 | function getPendingOrderKeys() external view returns (uint64[] memory){ 155 | return _getPendingOrders(); 156 | } 157 | 158 | function withdraw() external nonReentrant { 159 | _updateConfigs(); 160 | _cleanOrders(); 161 | 162 | uint256 ethBalance = address(this).balance; 163 | if (ethBalance > 0) { 164 | AddressUpgradeable.sendValue(payable(_account.account), ethBalance); 165 | emit Withdraw( 166 | _account.collateralToken, 167 | _account.account, 168 | ethBalance 169 | ); 170 | } 171 | uint256 balance = IERC20Upgradeable(_account.collateralToken).balanceOf(address(this)); 172 | if (balance > 0) { 173 | _transferToUser(balance, _account.collateralToken); 174 | emit Withdraw( 175 | _account.collateralToken, 176 | _account.account, 177 | balance 178 | ); 179 | } 180 | if(_account.profitTokenAddress != _account.collateralToken){ 181 | uint256 profitTokenBalance = IERC20Upgradeable(_account.profitTokenAddress).balanceOf(address(this)); 182 | if(profitTokenBalance > 0){ 183 | _transferToUser(profitTokenBalance, _account.profitTokenAddress); 184 | emit Withdraw( 185 | _account.profitTokenAddress, 186 | _account.account, 187 | profitTokenBalance 188 | ); 189 | } 190 | } 191 | } 192 | 193 | function withdraw_tokens(address tokenAddress) external nonReentrant { 194 | uint256 tokenBalance = IERC20MetadataUpgradeable(tokenAddress).balanceOf(address(this)); 195 | if(tokenBalance > 0){ 196 | _transferToUser(tokenBalance, tokenAddress); 197 | emit Withdraw( 198 | tokenAddress, 199 | _account.account, 200 | tokenBalance 201 | ); 202 | } 203 | } 204 | 205 | function _transferToUser(uint256 amount, address tokenAddress) internal { 206 | if (tokenAddress == _WETH) { 207 | IWETH(_WETH).withdraw(amount); 208 | Address.sendValue(payable(_account.account), amount); 209 | } else { 210 | IERC20Upgradeable(tokenAddress).safeTransfer(_account.account, amount); 211 | } 212 | } 213 | 214 | function _isMarketOrder(uint8 flags) internal pure returns (bool) { 215 | return (flags & POSITION_MARKET_ORDER) != 0; 216 | } 217 | 218 | function _cleanOrders() internal { 219 | uint64[] memory pendingKeys = _pendingOrders; 220 | for (uint256 i = 0; i < pendingKeys.length; i++) { 221 | uint64 key = pendingKeys[i]; 222 | ( ,bool orderExists) = LibMux.getOrder(_exchangeConfigs, key); 223 | if (!orderExists) { 224 | _removePendingOrder(key); 225 | } 226 | } 227 | } 228 | 229 | } -------------------------------------------------------------------------------- /src/aggregators/mux/Position.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity 0.8.19; 3 | 4 | import "../../../lib/openzeppelin-contracts-upgradeable/contracts/utils/math/MathUpgradeable.sol"; 5 | import "../../interfaces/IMuxGetter.sol"; 6 | import "./lib/LibMux.sol"; 7 | import "../lib/LibMath.sol"; 8 | 9 | import "./Storage.sol"; 10 | import "./Types.sol"; 11 | 12 | contract Position is Storage{ 13 | using MathUpgradeable for uint256; 14 | using LibMath for uint256; 15 | 16 | uint256 internal constant MAX_PENDING_ORDERS = 64; 17 | 18 | event AddPendingOrder( 19 | LibMux.OrderCategory category, 20 | uint256 index, 21 | uint256 timestamp 22 | ); 23 | event RemovePendingOrder(uint64 orderId); 24 | event CancelOrder(uint64 orderId, bool success); 25 | 26 | event OpenPosition(uint8 collateralId, uint8 indexId, bool isLong, PositionContext context); 27 | event ClosePosition(uint8 collateralId, uint8 indexId, bool isLong, PositionContext context); 28 | 29 | function _hasPendingOrder(uint64 key) internal view returns (bool) { 30 | return _pendingOrdersContains(key); 31 | } 32 | 33 | function _getPendingOrders() internal view returns (uint64[] memory) { 34 | return _pendingOrders; 35 | } 36 | 37 | function _removePendingOrder(uint64 key) internal { 38 | _pendingOrdersRemove(key); 39 | emit RemovePendingOrder(key); 40 | } 41 | 42 | function _addPendingOrder( 43 | LibMux.OrderCategory category, 44 | uint256 startOrderCount, 45 | uint256 endOrderCount, 46 | bytes32 subAccountId 47 | ) internal{ 48 | (bytes32[3][] memory orderArray, ) = IMuxOrderBook(_exchangeConfigs.orderBook).getOrders(startOrderCount, endOrderCount); 49 | uint256 totalCount = endOrderCount - startOrderCount; 50 | for (uint256 i = 0; i < totalCount; i++) { 51 | PositionOrder memory order = LibMux.decodePositionOrder(orderArray[i]); 52 | if(order.subAccountId == subAccountId) { 53 | require( 54 | _pendingOrdersAdd(order.id), 55 | "AddFailed" 56 | ); 57 | emit AddPendingOrder(category, i, block.timestamp); 58 | } 59 | } 60 | } 61 | 62 | function _isMarginSafe(SubAccount memory subAccount, uint96 collateralPrice, uint96 assetPrice, bool isLong, uint96 collateralDelta, uint96 sizeDelta, bool isOpen) internal view returns(bool){ 63 | //for closing a position, we dont have to consider the delta in size and collateral, we just want to make sure the current position margin is safe. 64 | collateralDelta = isOpen ? collateralDelta : 0; 65 | sizeDelta = isOpen ? sizeDelta : 0; 66 | 67 | if(subAccount.size == 0){ 68 | return true; 69 | } 70 | Margin memory margin; 71 | margin.asset = IMuxGetter(_exchangeConfigs.liquidityPool).getAssetInfo(_account.indexId); 72 | bool hasProfit; 73 | if (subAccount.size != 0) { 74 | (hasProfit, margin.muxPnlUsd) = LibMux._positionPnlUsd(margin.asset, subAccount, isLong, assetPrice); 75 | margin.muxFundingFeeUsd = LibMux._getFundingFeeUsd(subAccount, margin.asset, isLong, assetPrice); 76 | } 77 | 78 | //We dont have to add sizeDelta to liquidation fees since we are only concerned about the current position liquidating 79 | margin.liquidationFeeUsd = LibMux._getLiquidationFeeUsd(margin.asset, subAccount.size, assetPrice); 80 | 81 | //Minimum threshold the position has to maintain 82 | uint32 threshold = isOpen ? margin.asset.initialMarginRate : margin.asset.maintenanceMarginRate; 83 | margin.thresholdUsd = ((uint256(subAccount.size) + uint256(sizeDelta)) * uint256(assetPrice) * uint256(threshold)) / 1e18 / 1e5; 84 | 85 | //Collateral left in the position 86 | uint256 collateralUsd = ((uint256(subAccount.collateral) + uint256(collateralDelta)) * collateralPrice) / uint256(10**_account.collateralDecimals); 87 | 88 | return ((hasProfit ? collateralUsd + margin.muxPnlUsd - margin.muxFundingFeeUsd : collateralUsd - margin.muxPnlUsd - margin.muxFundingFeeUsd) >= margin.thresholdUsd.max(margin.liquidationFeeUsd)); 89 | } 90 | 91 | function _placePositionOrder(PositionContext memory context) internal{ 92 | require(_pendingOrders.length <= MAX_PENDING_ORDERS, "TooManyPendingOrders"); 93 | 94 | SubAccount memory subAccount; 95 | (subAccount.collateral, subAccount.size, subAccount.lastIncreasedTime, subAccount.entryPrice, subAccount.entryFunding) = IMuxGetter(_exchangeConfigs.liquidityPool).getSubAccount(context.subAccountId); 96 | 97 | bool isOpen = ((context.flags & POSITION_OPEN) != 0) ? true : false; 98 | 99 | require( 100 | _isMarginSafe( 101 | subAccount, 102 | context.collateralPrice, 103 | context.assetPrice, 104 | context.isLong, 105 | context.collateralAmount, 106 | context.size, 107 | isOpen 108 | ), 109 | "ImMarginUnsafe" 110 | ); 111 | 112 | uint256 startOrderCount = IMuxOrderBook(_exchangeConfigs.orderBook).getOrderCount(); 113 | IMuxOrderBook(_exchangeConfigs.orderBook).placePositionOrder3{ value: msg.value }(context.subAccountId, context.collateralAmount, context.size, context.price, context.profitTokenId ,context.flags, context.deadline, _exchangeConfigs.referralCode, context.extra); 114 | uint256 endOrderCount = IMuxOrderBook(_exchangeConfigs.orderBook).getOrderCount(); 115 | 116 | require(endOrderCount > startOrderCount, "Order not recorded on MUX"); 117 | 118 | if(isOpen){ 119 | _addPendingOrder(LibMux.OrderCategory.OPEN, startOrderCount, endOrderCount, context.subAccountId); 120 | emit OpenPosition(_account.collateralId, _account.indexId, context.isLong, context); 121 | }else{ 122 | _addPendingOrder(LibMux.OrderCategory.CLOSE, startOrderCount, endOrderCount, context.subAccountId); 123 | emit ClosePosition(_account.collateralId, _account.indexId, context.isLong, context); 124 | } 125 | } 126 | 127 | function _cancelOrder(uint64 orderId) internal returns(bool success){ 128 | success = LibMux.cancelOrderFromOrderBook(_exchangeConfigs.orderBook, orderId); 129 | if(success) { 130 | if(_hasPendingOrder(orderId)){ 131 | _removePendingOrder(orderId); 132 | } 133 | emit CancelOrder(orderId, success); 134 | } 135 | } 136 | 137 | // ======================== Utility methods ======================== 138 | 139 | function _pendingOrdersContains(uint64 value) internal view returns(bool){ 140 | for(uint i = 0; i < _pendingOrders.length; i++) { 141 | if (_pendingOrders[i] == value) { 142 | return true; 143 | } 144 | } 145 | return false; 146 | } 147 | 148 | function _pendingOrdersAdd(uint64 value) internal returns(bool){ 149 | uint initialLength = _pendingOrders.length; 150 | _pendingOrders.push(value); 151 | if (_pendingOrders.length == initialLength + 1) { 152 | return true; 153 | } else { 154 | return false; 155 | } 156 | } 157 | 158 | function _pendingOrdersRemove(uint64 value) internal { 159 | uint i = 0; 160 | bool found = false; 161 | for (; i < _pendingOrders.length; i++) { 162 | if (_pendingOrders[i] == value) { 163 | found = true; 164 | break; 165 | } 166 | } 167 | 168 | if (found) { 169 | // Set the i-th element to the last element 170 | _pendingOrders[i] = _pendingOrders[_pendingOrders.length - 1]; 171 | // Remove the last element 172 | _pendingOrders.pop(); 173 | } 174 | } 175 | 176 | } -------------------------------------------------------------------------------- /src/aggregators/mux/Storage.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity 0.8.19; 3 | 4 | import "../../../lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol"; 5 | import "../../../lib/openzeppelin-contracts-upgradeable/contracts/utils/structs/EnumerableSetUpgradeable.sol"; 6 | 7 | import "./Types.sol"; 8 | 9 | contract Storage is Initializable{ 10 | uint256 internal constant EXCHANGE_ID = 2; 11 | 12 | address internal _factory; 13 | bytes32 internal _subAccountId; 14 | 15 | uint32 internal _localexchangeVersion; 16 | mapping(address => uint32) _localAssetVersions; 17 | 18 | AccountState internal _account; 19 | ExchangeConfigs internal _exchangeConfigs; 20 | 21 | uint64[] internal _pendingOrders; 22 | 23 | //MUX Position Constants 24 | uint8 constant POSITION_OPEN = 0x80; // this flag means openPosition; otherwise closePosition 25 | uint8 constant POSITION_MARKET_ORDER = 0x40; // this flag means ignore limitPrice 26 | uint8 constant POSITION_WITHDRAW_ALL_IF_EMPTY = 0x20; // this flag means auto withdraw all collateral if position.size == 0 27 | uint8 constant POSITION_TRIGGER_ORDER = 0x10; // this flag means this is a trigger order (ex: stop-loss order). otherwise this is a limit order (ex: take-profit order) 28 | uint8 constant POSITION_TPSL_STRATEGY = 0x08; // for open-position-order, this flag auto place take-profit and stop-loss orders when open-position-order fills. 29 | // for close-position-order, this flag means ignore limitPrice and profitTokenId, and use extra.tpPrice, extra.slPrice, extra.tpslProfitTokenId instead. 30 | 31 | } -------------------------------------------------------------------------------- /src/aggregators/mux/Types.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity 0.8.19; 3 | 4 | enum ExchangeConfigIds { 5 | LIQUIDITY_POOL, 6 | ORDER_BOOK, 7 | REFERRAL_CODE, 8 | END 9 | } 10 | 11 | struct AccountState { 12 | address account; 13 | address collateralToken; 14 | uint8 collateralId; 15 | uint8 indexId; // 160 16 | address profitTokenAddress; 17 | bool isLong; // 8 18 | uint8 collateralDecimals; 19 | bytes32[20] reserved; 20 | } 21 | 22 | struct ExchangeConfigs { 23 | address liquidityPool; 24 | address orderBook; 25 | bytes32 referralCode; 26 | // ======================== 27 | bytes32[20] reserved; 28 | } 29 | 30 | struct PositionContext { 31 | // parameters 32 | uint96 collateralAmount; 33 | uint96 size; 34 | uint96 price; 35 | uint8 flags; 36 | uint96 assetPrice; 37 | uint96 collateralPrice; 38 | uint8 profitTokenId; 39 | bytes32 subAccountId; 40 | uint32 deadline; 41 | bool isLong; 42 | PositionOrderExtra extra; 43 | } 44 | 45 | struct ClosePositionContext { 46 | uint256 collateralUsd; 47 | uint256 sizeUsd; 48 | uint256 priceUsd; 49 | bool isMarket; 50 | uint256 subAccountId; 51 | } 52 | 53 | struct SubAccount { 54 | // slot 55 | uint96 collateral; 56 | uint96 size; 57 | uint32 lastIncreasedTime; 58 | // slot 59 | uint96 entryPrice; 60 | uint128 entryFunding; // entry longCumulativeFundingRate for long position. entry shortCumulativeFunding for short position 61 | } 62 | 63 | struct Asset { 64 | // slot 65 | // assets with the same symbol in different chains are the same asset. they shares the same muxToken. so debts of the same symbol 66 | // can be accumulated across chains (see Reader.AssetState.deduct). ex: ERC20(fBNB).symbol should be "BNB", so that BNBs of 67 | // different chains are the same. 68 | // since muxToken of all stable coins is the same and is calculated separately (see Reader.ChainState.stableDeduct), stable coin 69 | // symbol can be different (ex: "USDT", "USDT.e" and "fUSDT"). 70 | bytes32 symbol; 71 | // slot 72 | address tokenAddress; // erc20.address 73 | uint8 id; 74 | uint8 decimals; // erc20.decimals 75 | uint56 flags; // a bitset of ASSET_* 76 | uint24 _flagsPadding; 77 | // slot 78 | uint32 initialMarginRate; // 1e5 79 | uint32 maintenanceMarginRate; // 1e5 80 | uint32 minProfitRate; // 1e5 81 | uint32 minProfitTime; // 1e0 82 | uint32 positionFeeRate; // 1e5 83 | // note: 96 bits remaining 84 | // slot 85 | address referenceOracle; 86 | uint32 referenceDeviation; // 1e5 87 | uint8 referenceOracleType; 88 | uint32 halfSpread; // 1e5 89 | // note: 24 bits remaining 90 | // slot 91 | uint96 credit; 92 | uint128 _reserved2; 93 | // slot 94 | uint96 collectedFee; 95 | uint32 liquidationFeeRate; // 1e5 96 | uint96 spotLiquidity; 97 | // note: 32 bits remaining 98 | // slot 99 | uint96 maxLongPositionSize; 100 | uint96 totalLongPosition; 101 | // note: 64 bits remaining 102 | // slot 103 | uint96 averageLongPrice; 104 | uint96 maxShortPositionSize; 105 | // note: 64 bits remaining 106 | // slot 107 | uint96 totalShortPosition; 108 | uint96 averageShortPrice; 109 | // note: 64 bits remaining 110 | // slot, less used 111 | address muxTokenAddress; // muxToken.address. all stable coins share the same muxTokenAddress 112 | uint32 spotWeight; // 1e0 113 | uint32 longFundingBaseRate8H; // 1e5 114 | uint32 longFundingLimitRate8H; // 1e5 115 | // slot 116 | uint128 longCumulativeFundingRate; // Σ_t fundingRate_t 117 | uint128 shortCumulativeFunding; // Σ_t fundingRate_t * indexPrice_t 118 | } 119 | 120 | struct PositionOrder { 121 | uint64 id; 122 | bytes32 subAccountId; // 160 + 8 + 8 + 8 = 184 123 | uint96 collateral; // erc20.decimals 124 | uint96 size; // 1e18 125 | uint96 price; // 1e18 126 | uint8 profitTokenId; 127 | uint8 flags; 128 | uint32 placeOrderTime; // 1e0 129 | uint24 expire10s; // 10 seconds. deadline = placeOrderTime + expire * 10 130 | } 131 | 132 | struct PositionOrderExtra { 133 | // tp/sl strategy 134 | uint96 tpPrice; // take-profit price. decimals = 18. only valid when flags.POSITION_TPSL_STRATEGY. 135 | uint96 slPrice; // stop-loss price. decimals = 18. only valid when flags.POSITION_TPSL_STRATEGY. 136 | uint8 tpslProfitTokenId; // only valid when flags.POSITION_TPSL_STRATEGY. 137 | uint32 tpslDeadline; // only valid when flags.POSITION_TPSL_STRATEGY. 138 | } 139 | 140 | struct Margin { 141 | Asset asset; 142 | uint96 muxPnlUsd; 143 | uint96 muxFundingFeeUsd; 144 | uint96 liquidationFeeUsd; 145 | uint256 thresholdUsd; 146 | } -------------------------------------------------------------------------------- /src/aggregators/mux/lib/LibMux.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity 0.8.19; 3 | 4 | import "../../../interfaces/IMuxOrderBook.sol"; 5 | import "../../lib/LibMath.sol"; 6 | 7 | import "../Types.sol"; 8 | 9 | library LibMux { 10 | using LibMath for uint256; 11 | 12 | enum OrderCategory { 13 | NONE, 14 | OPEN, 15 | CLOSE 16 | } 17 | 18 | function getOrder(ExchangeConfigs memory exchangeConfigs, uint64 orderId) internal view returns(bytes32[3] memory order, bool isOrderPresent){ 19 | (order, isOrderPresent) = IMuxOrderBook(exchangeConfigs.orderBook).getOrder(orderId); 20 | } 21 | 22 | function _getFundingFeeUsd( 23 | SubAccount memory subAccount, 24 | Asset memory asset, 25 | bool isLong, 26 | uint96 assetPrice 27 | ) internal pure returns (uint96) { 28 | if (subAccount.size == 0) { 29 | return 0; 30 | } 31 | uint256 cumulativeFunding; 32 | if (isLong) { 33 | cumulativeFunding = asset.longCumulativeFundingRate - subAccount.entryFunding; 34 | cumulativeFunding = cumulativeFunding.wmul(assetPrice); 35 | } else { 36 | cumulativeFunding = asset.shortCumulativeFunding - subAccount.entryFunding; 37 | } 38 | return cumulativeFunding.wmul(subAccount.size).safeUint96(); 39 | } 40 | 41 | function _positionPnlUsd( 42 | Asset memory asset, 43 | SubAccount memory subAccount, 44 | bool isLong, 45 | uint96 assetPrice 46 | ) internal view returns (bool hasProfit, uint96 pnlUsd) { 47 | if (subAccount.size == 0) { 48 | return (false, 0); 49 | } 50 | require(assetPrice > 0, "P=0"); // Price Is Zero 51 | hasProfit = isLong ? assetPrice > subAccount.entryPrice : assetPrice < subAccount.entryPrice; 52 | uint96 priceDelta = assetPrice >= subAccount.entryPrice 53 | ? assetPrice - subAccount.entryPrice 54 | : subAccount.entryPrice - assetPrice; 55 | if ( 56 | hasProfit && 57 | _blockTimestamp() < subAccount.lastIncreasedTime + asset.minProfitTime && 58 | priceDelta < uint256(subAccount.entryPrice).rmul(asset.minProfitRate).safeUint96() 59 | ) { 60 | hasProfit = false; 61 | return (false, 0); 62 | } 63 | pnlUsd = uint256(priceDelta).wmul(subAccount.size).safeUint96(); 64 | } 65 | 66 | function _isAccountSafe( 67 | uint256 thresholdUsd, 68 | uint256 collateralUsd, 69 | bool hasProfit, 70 | uint96 pnlUsd, 71 | uint96 fundingFee,// fundingFee = 0 if subAccount.collateral was modified 72 | uint96 liquidationFeeUsd 73 | ) internal pure returns (bool) { 74 | 75 | // break down "collateralUsd +/- pnlUsd >= thresholdUsd >= 0" 76 | 77 | } 78 | 79 | function _getLiquidationFeeUsd( 80 | Asset memory asset, 81 | uint96 amount, 82 | uint96 assetPrice 83 | ) internal pure returns (uint96) { 84 | uint256 feeUsd = ((uint256(assetPrice) * uint256(asset.liquidationFeeRate)) * uint256(amount)) / 1e5 / 1e18; 85 | return feeUsd.safeUint96(); 86 | } 87 | 88 | function _blockTimestamp() internal view returns (uint32) { 89 | return uint32(block.timestamp); 90 | } 91 | 92 | // check Types.PositionOrder for schema 93 | function decodePositionOrder(bytes32[3] memory data) internal pure returns (PositionOrder memory order) { 94 | order.subAccountId = bytes32(bytes23(data[0])); 95 | order.id = uint64((uint256(data[0]) >> 8) & ((1 << 64) - 1)); 96 | order.collateral = uint96(bytes12(data[2] << 96)); 97 | order.size = uint96(bytes12(data[1])); 98 | order.flags = uint8(bytes1(data[1] << 104)); 99 | order.price = uint96(bytes12(data[2])); 100 | order.profitTokenId = uint8(bytes1(data[1] << 96)); 101 | order.expire10s = uint24(bytes3(data[1] << 136)); 102 | order.placeOrderTime = uint32(bytes4(data[1] << 160)); 103 | } 104 | 105 | function cancelOrderFromOrderBook(address orderBook, uint64 orderId) internal returns(bool success){ 106 | IMuxOrderBook(orderBook).cancelOrder(orderId); 107 | success = true; 108 | } 109 | } -------------------------------------------------------------------------------- /src/components/ImplementationGuard.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity 0.8.19; 3 | 4 | contract ImplementationGuard { 5 | address private immutable _this; 6 | 7 | constructor() { 8 | _this = address(this); 9 | } 10 | 11 | modifier onlyDelegateCall() { 12 | require(address(this) != _this); 13 | _; 14 | } 15 | } -------------------------------------------------------------------------------- /src/gmxProxyFactory/GmxProxyBeacon.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity 0.8.19; 3 | 4 | import "../../lib/openzeppelin-contracts/contracts/proxy/beacon/IBeacon.sol"; 5 | import "../../lib/openzeppelin-contracts/contracts/proxy/beacon/BeaconProxy.sol"; 6 | 7 | import "./GmxStorage.sol"; 8 | 9 | contract GmxProxyBeacon is GmxStorage, IBeacon{ 10 | event Upgraded(uint256 exchangeId, address indexed implementation); 11 | event CreateProxy( 12 | uint256 exchangeId, 13 | bytes32 proxyId, 14 | address owner, 15 | address proxy, 16 | address assetToken, 17 | address collateralToken, 18 | bool isLong 19 | ); 20 | 21 | function implementation() external view virtual override returns (address) { 22 | require(_isCreatedProxy(msg.sender), "NotProxy"); 23 | return _implementations[_proxyExchangeIds[msg.sender]]; 24 | } 25 | 26 | function _isCreatedProxy(address proxy_) internal view returns (bool) { 27 | return _proxyExchangeIds[proxy_] != 0; 28 | } 29 | 30 | function _setImplementation(uint256 exchangeId, address newImplementation_) internal { 31 | require(newImplementation_ != address(0), "ZeroImplementationAddress"); 32 | _implementations[exchangeId] = newImplementation_; 33 | } 34 | 35 | function _upgradeTo(uint256 exchangeId, address newImplementation_) internal virtual { 36 | _setImplementation(exchangeId, newImplementation_); 37 | emit Upgraded(exchangeId, newImplementation_); 38 | } 39 | 40 | function _createProxy( 41 | uint256 exchangeId, 42 | bytes32 proxyId, 43 | bytes memory bytecode 44 | ) internal returns (address proxy) { 45 | proxy = _getAddress(bytecode, proxyId); 46 | _proxyExchangeIds[proxy] = exchangeId; // IMPORTANT 47 | assembly { 48 | proxy := create2(0x0, add(0x20, bytecode), mload(bytecode), proxyId) 49 | } 50 | require(proxy != address(0), "CreateFailed"); 51 | } 52 | 53 | function _createBeaconProxy( 54 | uint256 exchangeId, 55 | address account, 56 | address assetToken, 57 | address collateralToken, 58 | bool isLong 59 | ) internal returns (address) { 60 | // require(exchangeId) // isValid 61 | bytes32 proxyId = _makeProxyId(exchangeId, account, collateralToken, assetToken, isLong); 62 | require(_tradingProxies[proxyId] == address(0), "AlreadyCreated"); 63 | bytes memory initData = abi.encodeWithSignature( 64 | "initialize(uint256,address,address,address,bool)", 65 | exchangeId, 66 | account, 67 | collateralToken, 68 | assetToken, 69 | isLong 70 | ); 71 | bytes memory bytecode = abi.encodePacked(type(BeaconProxy).creationCode, abi.encode(address(this), initData)); 72 | address proxy = _createProxy(exchangeId, proxyId, bytecode); 73 | _tradingProxies[proxyId] = proxy; 74 | _ownedProxies[account].push(proxy); 75 | emit CreateProxy(exchangeId, proxyId, account, proxy, assetToken, collateralToken, isLong); 76 | return proxy; 77 | } 78 | 79 | function _getAddress(bytes memory bytecode, bytes32 proxyId) internal view returns (address) { 80 | bytes32 hash = keccak256(abi.encodePacked(bytes1(0xff), address(this), proxyId, keccak256(bytecode))); 81 | return address(uint160(uint256(hash))); 82 | } 83 | 84 | function _makeProxyId( 85 | uint256 exchangeId_, 86 | address account_, 87 | address collateralToken_, 88 | address assetToken_, 89 | bool isLong_ 90 | ) internal pure returns (bytes32) { 91 | return keccak256(abi.encodePacked(exchangeId_, account_, collateralToken_, assetToken_, isLong_)); 92 | } 93 | } -------------------------------------------------------------------------------- /src/gmxProxyFactory/GmxProxyConfig.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity 0.8.19; 3 | 4 | import "./GmxStorage.sol"; 5 | 6 | contract GmxProxyConfig is GmxStorage{ 7 | 8 | event SetExchangeConfig(uint256 ExchangeId, uint256[] values, uint256 version); 9 | event SetExchangeAssetConfig(uint256 ExchangeId, address assetToken, uint256[] values, uint256 version); 10 | 11 | function _getLatestVersions(uint256 ExchangeId) 12 | internal 13 | view 14 | returns (uint32 exchangeConfigVersion) 15 | { 16 | exchangeConfigVersion = _exchangeConfigs[ExchangeId].version; 17 | } 18 | 19 | function _setExchangeConfig(uint256 ExchangeId, uint256[] memory values) internal { 20 | _exchangeConfigs[ExchangeId].values = values; 21 | _exchangeConfigs[ExchangeId].version += 1; 22 | emit SetExchangeConfig(ExchangeId, values, _exchangeConfigs[ExchangeId].version); 23 | } 24 | } -------------------------------------------------------------------------------- /src/gmxProxyFactory/GmxProxyFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity 0.8.19; 3 | 4 | import "../../lib/openzeppelin-contracts/contracts/proxy/beacon/BeaconProxy.sol"; 5 | import "../../lib/openzeppelin-contracts/contracts/proxy/beacon/IBeacon.sol"; 6 | 7 | import "../../lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol"; 8 | import "../../lib/openzeppelin-contracts-upgradeable/contracts/token/ERC20/IERC20Upgradeable.sol"; 9 | import "../../lib/openzeppelin-contracts-upgradeable/contracts/token/ERC20/utils/SafeERC20Upgradeable.sol"; 10 | 11 | import "../interfaces/IGmxAggregator.sol"; 12 | 13 | import "./GmxStorage.sol"; 14 | import "./GmxProxyBeacon.sol"; 15 | import "./GmxProxyConfig.sol"; 16 | 17 | contract GmxProxyFactory is GmxStorage, GmxProxyBeacon, GmxProxyConfig, OwnableUpgradeable { 18 | using SafeERC20Upgradeable for IERC20Upgradeable; 19 | 20 | struct OpenPositionArgs { 21 | uint256 exchangeId; 22 | address collateralToken; 23 | address assetToken; 24 | bool isLong; 25 | address tokenIn; 26 | uint256 amountIn; // tokenIn.decimals 27 | uint256 minOut; // collateral.decimals 28 | uint256 sizeUsd; // 1e18 29 | uint96 priceUsd; // 1e18 30 | uint96 tpPriceUsd; // 1e18 31 | uint96 slPriceUsd; // 1e18 32 | uint8 flags; // MARKET, TRIGGER 33 | bytes32 referralCode; 34 | } 35 | 36 | struct ClosePositionArgs { 37 | uint256 exchangeId; 38 | address collateralToken; 39 | address assetToken; 40 | bool isLong; 41 | uint256 collateralUsd; // collateral.decimals 42 | uint256 sizeUsd; // 1e18 43 | uint96 priceUsd; // 1e18 44 | uint96 tpPriceUsd; // 1e18 45 | uint96 slPriceUsd; // 1e18 46 | uint8 flags; // MARKET, TRIGGER 47 | bytes32 referralCode; 48 | } 49 | 50 | struct OrderParams { 51 | bytes32 orderKey; 52 | uint256 collateralDelta; 53 | uint256 sizeDelta; 54 | uint256 triggerPrice; 55 | bool triggerAboveThreshold; 56 | } 57 | 58 | event SetReferralCode(bytes32 referralCode); 59 | event SetMaintainer(address maintainer, bool enable); 60 | 61 | function initialize(address weth_) external initializer { 62 | __Ownable_init(); 63 | _weth = weth_; 64 | } 65 | 66 | function weth() external view returns (address) { 67 | return _weth; 68 | } 69 | 70 | // ======================== getter methods ======================== 71 | 72 | function getImplementationAddress(uint256 exchangeId) external view returns(address){ 73 | return _implementations[exchangeId]; 74 | } 75 | 76 | function getProxyExchangeId(address proxy) external view returns(uint256){ 77 | return _proxyExchangeIds[proxy]; 78 | } 79 | 80 | function getTradingProxy(bytes32 proxyId) external view returns(address){ 81 | return _tradingProxies[proxyId]; 82 | } 83 | 84 | function getProxiesOf(address account) public view returns (address[] memory) { 85 | return _ownedProxies[account]; 86 | } 87 | 88 | function getExchangeConfig(uint256 ExchangeId) external view returns (uint256[] memory) { 89 | return _exchangeConfigs[ExchangeId].values; 90 | } 91 | 92 | function getMainatinerStatus(address maintainer) external view returns(bool){ 93 | return _maintainers[maintainer]; 94 | } 95 | 96 | function getConfigVersions(uint256 ExchangeId) 97 | external 98 | view 99 | returns (uint32 exchangeConfigVersion) 100 | { 101 | return _getLatestVersions(ExchangeId); 102 | } 103 | 104 | // ======================== methods for contract management ======================== 105 | function upgradeTo(uint256 exchangeId, address newImplementation_) external onlyOwner { 106 | _upgradeTo(exchangeId, newImplementation_); 107 | } 108 | 109 | function setExchangeConfig(uint256 ExchangeId, uint256[] memory values) external { 110 | require(_maintainers[msg.sender] || msg.sender == owner(), "OnlyMaintainerOrAbove"); 111 | _setExchangeConfig(ExchangeId, values); 112 | } 113 | 114 | function setMaintainer(address maintainer, bool enable) external onlyOwner { 115 | _maintainers[maintainer] = enable; 116 | emit SetMaintainer(maintainer, enable); 117 | } 118 | 119 | // ======================== methods called by user ======================== 120 | function createProxy( 121 | uint256 exchangeId, 122 | address collateralToken, 123 | address assetToken, 124 | bool isLong 125 | ) public returns (address) { 126 | return 127 | _createBeaconProxy( 128 | exchangeId, 129 | msg.sender, 130 | assetToken, 131 | collateralToken, 132 | isLong 133 | ); 134 | } 135 | 136 | function openPosition(OpenPositionArgs calldata args) external payable { 137 | bytes32 proxyId = _makeProxyId(args.exchangeId, msg.sender, args.collateralToken, args.assetToken, args.isLong); 138 | address proxy = _tradingProxies[proxyId]; 139 | if (proxy == address(0)) { 140 | proxy = createProxy(args.exchangeId, args.collateralToken, args.assetToken, args.isLong); 141 | } 142 | if (args.tokenIn != _weth) { 143 | IERC20Upgradeable(args.tokenIn).safeTransferFrom(msg.sender, proxy, args.amountIn); 144 | } else { 145 | require(msg.value >= args.amountIn, "InsufficientAmountIn"); 146 | } 147 | 148 | IGmxAggregator(proxy).openPosition{ value: msg.value }( 149 | args.tokenIn, 150 | args.amountIn, 151 | args.minOut, 152 | args.sizeUsd, 153 | args.priceUsd, 154 | args.tpPriceUsd, 155 | args.slPriceUsd, 156 | args.flags 157 | ); 158 | } 159 | 160 | function closePosition(ClosePositionArgs calldata args) external payable { 161 | address proxy = _mustGetProxy(args.exchangeId, msg.sender, args.collateralToken, args.assetToken, args.isLong); 162 | 163 | IGmxAggregator(proxy).closePosition{ value: msg.value }( 164 | args.collateralUsd, 165 | args.sizeUsd, 166 | args.priceUsd, 167 | args.tpPriceUsd, 168 | args.slPriceUsd, 169 | args.flags 170 | ); 171 | } 172 | 173 | function cancelOrders( 174 | uint256 exchangeId, 175 | address collateralToken, 176 | address assetToken, 177 | bool isLong, 178 | bytes32[] calldata keys 179 | ) external { 180 | IGmxAggregator(_mustGetProxy(exchangeId, msg.sender, collateralToken, assetToken, isLong)).cancelOrders(keys); 181 | } 182 | 183 | function updateOrder( 184 | uint256 exchangeId, 185 | address collateralToken, 186 | address assetToken, 187 | bool isLong, 188 | OrderParams[] memory orderParams 189 | ) external { 190 | for (uint256 i = 0; i < orderParams.length; i++) { 191 | IGmxAggregator(_mustGetProxy(exchangeId, msg.sender, collateralToken, assetToken, isLong)).updateOrder( 192 | orderParams[i].orderKey, 193 | orderParams[i].collateralDelta, 194 | orderParams[i].sizeDelta, 195 | orderParams[i].triggerPrice, 196 | orderParams[i].triggerAboveThreshold 197 | ); 198 | } 199 | } 200 | 201 | function getPendingOrderKeys(uint256 exchangeId, address collateralToken, address assetToken, bool isLong) external view returns(bytes32[] memory){ 202 | return IGmxAggregator(_mustGetProxy(exchangeId, msg.sender, collateralToken, assetToken, isLong)).getPendingOrderKeys(); 203 | } 204 | 205 | function withdraw( 206 | uint256 exchangeId, 207 | address account, 208 | address collateralToken, 209 | address assetToken, 210 | bool isLong 211 | ) external { 212 | require(_maintainers[msg.sender] || msg.sender == owner(), "OnlyMaintainerOrAbove"); 213 | IGmxAggregator(_mustGetProxy(exchangeId, account, collateralToken, assetToken, isLong)).withdraw(); 214 | } 215 | 216 | function removeProxy(bytes32 proxyId, address userAddress) external onlyOwner { 217 | // Fetch the proxy address from _tradingProxies 218 | address proxyAddress = _tradingProxies[proxyId]; 219 | 220 | // Delete the entry in _tradingProxies 221 | delete _tradingProxies[proxyId]; 222 | 223 | // Delete the entry in _proxyExchangeIds 224 | delete _proxyExchangeIds[proxyAddress]; 225 | 226 | // Remove the proxyAddress from the _ownedProxies array for the userAddress 227 | address[] storage ownedProxies = _ownedProxies[userAddress]; 228 | for (uint i = 0; i < ownedProxies.length; i++) { 229 | if (ownedProxies[i] == proxyAddress) { 230 | // We found the proxyAddress, now we need to remove it 231 | ownedProxies[i] = ownedProxies[ownedProxies.length - 1]; 232 | ownedProxies.pop(); 233 | break; 234 | } 235 | } 236 | } 237 | 238 | // ======================== Methods called by maintainer ======================== 239 | 240 | function cancelTimeoutOrders( 241 | uint256 projectId, 242 | address account, 243 | address collateralToken, 244 | address assetToken, 245 | bool isLong, 246 | bytes32[] calldata keys 247 | ) external { 248 | IGmxAggregator(_mustGetProxy(projectId, account, collateralToken, assetToken, isLong)).cancelTimeoutOrders(keys); 249 | } 250 | 251 | // ======================== Utility methods ======================== 252 | function _mustGetProxy( 253 | uint256 exchangeId, 254 | address account, 255 | address collateralToken, 256 | address assetToken, 257 | bool isLong 258 | ) internal view returns (address proxy) { 259 | bytes32 proxyId = _makeProxyId(exchangeId, account, collateralToken, assetToken, isLong); 260 | proxy = _tradingProxies[proxyId]; 261 | require(proxy != address(0), "ProxyNotExist"); 262 | } 263 | } -------------------------------------------------------------------------------- /src/gmxProxyFactory/GmxStorage.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity 0.8.19; 3 | 4 | import "../../lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol"; 5 | 6 | contract GmxStorage is Initializable{ 7 | 8 | struct ConfigData { 9 | uint32 version; 10 | uint256[] values; 11 | } 12 | 13 | //mapping exchangeID to its implementation address 14 | mapping(uint256 => address) internal _implementations; 15 | //mapping proxy addresses to their exchangeIds 16 | mapping(address => uint256) internal _proxyExchangeIds; 17 | //mapping proxy IDs to their addresses 18 | mapping(bytes32 => address) internal _tradingProxies; 19 | //mapping user address to proxies owned by the user 20 | mapping(address => address[]) internal _ownedProxies; 21 | //mapping Exchange ID to Exchange Config Data 22 | mapping(uint256 => ConfigData) internal _exchangeConfigs; 23 | 24 | //mapping to identify maintainers of proxy factory 25 | mapping(address => bool) _maintainers; 26 | 27 | //record weth address 28 | address internal _weth; 29 | 30 | //address for logX referral manager 31 | address internal _referralManager; 32 | } -------------------------------------------------------------------------------- /src/interfaces/IGmxAggregator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity 0.8.19; 3 | 4 | import "../aggregators/gmx/Types.sol"; 5 | import "../aggregators/gmx/lib/LibGmx.sol"; 6 | 7 | interface IGmxAggregator { 8 | function initialize( 9 | uint256 projectId, 10 | address account, 11 | address collateralToken, 12 | address assetToken, 13 | bool isLong 14 | ) external; 15 | 16 | function accountState() external returns(AccountState memory); 17 | 18 | function getPositionKey() external view returns(bytes32); 19 | 20 | function getOrder(bytes32 orderKey) external view returns(bool isFilled, LibGmx.OrderHistory memory history); 21 | 22 | function openPosition( 23 | address swapInToken, 24 | uint256 swapInAmount, // tokenIn.decimals 25 | uint256 minSwapOut, // collateral.decimals 26 | uint256 sizeUsd, // 1e18 27 | uint96 priceUsd, // 1e18 28 | uint96 tpPriceUsd, 29 | uint96 slPriceUsd, 30 | uint8 flags // MARKET, TRIGGER 31 | ) external payable; 32 | 33 | function closePosition( 34 | uint256 collateralUsd, // collateral.decimals 35 | uint256 sizeUsd, // 1e18 36 | uint96 priceUsd, // 1e18 37 | uint96 tpPriceUsd, // 1e18 38 | uint96 slPriceUsd, // 1e18 39 | uint8 flags // MARKET, TRIGGER 40 | ) external payable; 41 | 42 | function updateOrder( 43 | bytes32 orderKey, 44 | uint256 collateralDelta, 45 | uint256 sizeDelta, 46 | uint256 triggerPrice, 47 | bool triggerAboveThreshold 48 | ) external; 49 | 50 | function withdraw() external; 51 | 52 | function cancelOrders(bytes32[] calldata keys) external; 53 | 54 | function cancelTimeoutOrders(bytes32[] calldata keys) external; 55 | 56 | function getPendingOrderKeys() external view returns (bytes32[] memory); 57 | } -------------------------------------------------------------------------------- /src/interfaces/IGmxOrderBook.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.19; 3 | 4 | interface IGmxOrderBook { 5 | event CreateIncreaseOrder( 6 | address indexed account, 7 | uint256 orderIndex, 8 | address purchaseToken, 9 | uint256 purchaseTokenAmount, 10 | address collateralToken, 11 | address indexToken, 12 | uint256 sizeDelta, 13 | bool isLong, 14 | uint256 triggerPrice, 15 | bool triggerAboveThreshold, 16 | uint256 executionFee 17 | ); 18 | event CancelIncreaseOrder( 19 | address indexed account, 20 | uint256 orderIndex, 21 | address purchaseToken, 22 | uint256 purchaseTokenAmount, 23 | address collateralToken, 24 | address indexToken, 25 | uint256 sizeDelta, 26 | bool isLong, 27 | uint256 triggerPrice, 28 | bool triggerAboveThreshold, 29 | uint256 executionFee 30 | ); 31 | event ExecuteIncreaseOrder( 32 | address indexed account, 33 | uint256 orderIndex, 34 | address purchaseToken, 35 | uint256 purchaseTokenAmount, 36 | address collateralToken, 37 | address indexToken, 38 | uint256 sizeDelta, 39 | bool isLong, 40 | uint256 triggerPrice, 41 | bool triggerAboveThreshold, 42 | uint256 executionFee, 43 | uint256 executionPrice 44 | ); 45 | event UpdateIncreaseOrder( 46 | address indexed account, 47 | uint256 orderIndex, 48 | address collateralToken, 49 | address indexToken, 50 | bool isLong, 51 | uint256 sizeDelta, 52 | uint256 triggerPrice, 53 | bool triggerAboveThreshold 54 | ); 55 | event CreateDecreaseOrder( 56 | address indexed account, 57 | uint256 orderIndex, 58 | address collateralToken, 59 | uint256 collateralDelta, 60 | address indexToken, 61 | uint256 sizeDelta, 62 | bool isLong, 63 | uint256 triggerPrice, 64 | bool triggerAboveThreshold, 65 | uint256 executionFee 66 | ); 67 | event CancelDecreaseOrder( 68 | address indexed account, 69 | uint256 orderIndex, 70 | address collateralToken, 71 | uint256 collateralDelta, 72 | address indexToken, 73 | uint256 sizeDelta, 74 | bool isLong, 75 | uint256 triggerPrice, 76 | bool triggerAboveThreshold, 77 | uint256 executionFee 78 | ); 79 | event ExecuteDecreaseOrder( 80 | address indexed account, 81 | uint256 orderIndex, 82 | address collateralToken, 83 | uint256 collateralDelta, 84 | address indexToken, 85 | uint256 sizeDelta, 86 | bool isLong, 87 | uint256 triggerPrice, 88 | bool triggerAboveThreshold, 89 | uint256 executionFee, 90 | uint256 executionPrice 91 | ); 92 | event UpdateDecreaseOrder( 93 | address indexed account, 94 | uint256 orderIndex, 95 | address collateralToken, 96 | uint256 collateralDelta, 97 | address indexToken, 98 | uint256 sizeDelta, 99 | bool isLong, 100 | uint256 triggerPrice, 101 | bool triggerAboveThreshold 102 | ); 103 | 104 | function minExecutionFee() external view returns (uint256); 105 | 106 | function getIncreaseOrder( 107 | address _account, 108 | uint256 _orderIndex 109 | ) 110 | external 111 | view 112 | returns ( 113 | address purchaseToken, 114 | uint256 purchaseTokenAmount, 115 | address collateralToken, 116 | address indexToken, 117 | uint256 sizeDelta, 118 | bool isLong, 119 | uint256 triggerPrice, 120 | bool triggerAboveThreshold, 121 | uint256 executionFee 122 | ); 123 | 124 | function getDecreaseOrder( 125 | address _account, 126 | uint256 _orderIndex 127 | ) 128 | external 129 | view 130 | returns ( 131 | address collateralToken, 132 | uint256 collateralDelta, 133 | address indexToken, 134 | uint256 sizeDelta, 135 | bool isLong, 136 | uint256 triggerPrice, 137 | bool triggerAboveThreshold, 138 | uint256 executionFee 139 | ); 140 | 141 | function increaseOrdersIndex(address account) external view returns (uint256); 142 | 143 | function decreaseOrdersIndex(address account) external view returns (uint256); 144 | 145 | function cancelMultiple( 146 | uint256[] memory _swapOrderIndexes, 147 | uint256[] memory _increaseOrderIndexes, 148 | uint256[] memory _decreaseOrderIndexes 149 | ) external; 150 | 151 | function createIncreaseOrder( 152 | address[] memory _path, 153 | uint256 _amountIn, 154 | address _indexToken, 155 | uint256 _minOut, 156 | uint256 _sizeDelta, 157 | address _collateralToken, 158 | bool _isLong, 159 | uint256 _triggerPrice, 160 | bool _triggerAboveThreshold, 161 | uint256 _executionFee, 162 | bool _shouldWrap 163 | ) external payable; 164 | 165 | function cancelIncreaseOrder(uint256 _orderIndex) external; 166 | 167 | function createDecreaseOrder( 168 | address _indexToken, 169 | uint256 _sizeDelta, 170 | address _collateralToken, 171 | uint256 _collateralDelta, 172 | bool _isLong, 173 | uint256 _triggerPrice, 174 | bool _triggerAboveThreshold 175 | ) external payable; 176 | 177 | function cancelDecreaseOrder(uint256 _orderIndex) external; 178 | 179 | function updateIncreaseOrder( 180 | uint256 _orderIndex, 181 | uint256 _sizeDelta, 182 | uint256 _triggerPrice, 183 | bool _triggerAboveThreshold 184 | ) external; 185 | 186 | function updateDecreaseOrder( 187 | uint256 _orderIndex, 188 | uint256 _collateralDelta, 189 | uint256 _sizeDelta, 190 | uint256 _triggerPrice, 191 | bool _triggerAboveThreshold 192 | ) external; 193 | 194 | function executeDecreaseOrder(address, uint256, address payable) external; 195 | 196 | function executeIncreaseOrder(address, uint256, address payable) external; 197 | } -------------------------------------------------------------------------------- /src/interfaces/IGmxPositionRouter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity 0.8.19; 4 | 5 | interface IGmxPositionRouter { 6 | struct IncreasePositionRequest { 7 | address account; 8 | // address[] path; 9 | address indexToken; 10 | uint256 amountIn; 11 | uint256 minOut; 12 | uint256 sizeDelta; 13 | bool isLong; 14 | uint256 acceptablePrice; 15 | uint256 executionFee; 16 | uint256 blockNumber; 17 | uint256 blockTime; 18 | bool hasCollateralInETH; 19 | } 20 | 21 | struct DecreasePositionRequest { 22 | address account; 23 | // address[] path; 24 | address indexToken; 25 | uint256 collateralDelta; 26 | uint256 sizeDelta; 27 | bool isLong; 28 | address receiver; 29 | uint256 acceptablePrice; 30 | uint256 minOut; 31 | uint256 executionFee; 32 | uint256 blockNumber; 33 | uint256 blockTime; 34 | bool withdrawETH; 35 | } 36 | 37 | function setPositionKeeper(address _account, bool _isActive) external; 38 | 39 | function increasePositionRequests(bytes32) external view returns (IncreasePositionRequest memory); 40 | 41 | function decreasePositionRequests(bytes32) external view returns (DecreasePositionRequest memory); 42 | 43 | function minExecutionFee() external view returns (uint256); 44 | 45 | function increasePositionsIndex(address account) external view returns (uint256); 46 | 47 | function decreasePositionsIndex(address account) external view returns (uint256); 48 | 49 | function createIncreasePosition( 50 | address[] memory _path, 51 | address _indexToken, 52 | uint256 _amountIn, 53 | uint256 _minOut, 54 | uint256 _sizeDelta, 55 | bool _isLong, 56 | uint256 _acceptablePrice, 57 | uint256 _executionFee, 58 | bytes32 _referralCode, 59 | address _callbackTarget 60 | ) external payable returns (bytes32); 61 | 62 | function createDecreasePosition( 63 | address[] memory _path, 64 | address _indexToken, 65 | uint256 _collateralDelta, 66 | uint256 _sizeDelta, 67 | bool _isLong, 68 | address _receiver, 69 | uint256 _acceptablePrice, 70 | uint256 _minOut, 71 | uint256 _executionFee, 72 | bool _withdrawETH, 73 | address _callbackTarget 74 | ) external payable returns (bytes32); 75 | 76 | function cancelIncreasePosition(bytes32 _key, address payable _executionFeeReceiver) external returns (bool); // callback 77 | 78 | function cancelDecreasePosition(bytes32 _key, address payable _executionFeeReceiver) external returns (bool); // callback 79 | 80 | function executeIncreasePosition(bytes32 _key, address payable _executionFeeReceiver) external returns (bool); 81 | 82 | function executeDecreasePosition(bytes32 _key, address payable _executionFeeReceiver) external returns (bool); 83 | } -------------------------------------------------------------------------------- /src/interfaces/IGmxProxyFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity 0.8.19; 3 | 4 | interface IGmxProxyFactory { 5 | 6 | struct OpenPositionArgs { 7 | uint256 exchangeId; 8 | address collateralToken; 9 | address assetToken; 10 | bool isLong; 11 | address tokenIn; 12 | uint256 amountIn; 13 | uint256 minOut; 14 | uint256 sizeUsd; 15 | uint96 priceUsd; 16 | uint8 flags; 17 | bytes32 referralCode; 18 | } 19 | 20 | struct ClosePositionArgs { 21 | uint256 exchangeId; 22 | address collateralToken; 23 | address assetToken; 24 | bool isLong; 25 | uint256 collateralUsd; 26 | uint256 sizeUsd; 27 | uint96 priceUsd; 28 | uint8 flags; 29 | bytes32 referralCode; 30 | } 31 | 32 | 33 | event SetReferralCode(bytes32 referralCode); 34 | event SetMaintainer(address maintainer, bool enable); 35 | 36 | function initialize(address weth_) external; 37 | function weth() external view returns (address); 38 | function implementation() external view returns(address); 39 | function getImplementationAddress(uint256 exchangeId) external view returns(address); 40 | function getProxyExchangeId(address proxy) external view returns(uint256); 41 | function getTradingProxy(bytes32 proxyId) external view returns(address); 42 | function getExchangeConfig(uint256 ExchangeId) external view returns (uint256[] memory); 43 | function upgradeTo(uint256 exchangeId, address newImplementation_) external; 44 | function setExchangeConfig(uint256 ExchangeId, uint256[] memory values) external; 45 | function getConfigVersions(uint256 ExchangeId) external view returns (uint32 exchangeConfigVersion); 46 | function setMaintainer(address maintainer, bool enable) external; 47 | function createProxy(uint256 exchangeId, address collateralToken, address assetToken, bool isLong) external returns (address); 48 | function openPosition(OpenPositionArgs calldata args) external payable; 49 | function closePosition(ClosePositionArgs calldata args) external payable; 50 | function cancelOrders(uint256 exchangeId, address collateralToken, address assetToken, bool isLong, bytes32[] calldata keys) external; 51 | function getPendingOrderKeys(uint256 exchangeId, address collateralToken, address assetToken, bool isLong) external view returns(bytes32[] memory); 52 | function withdraw(uint256 exchangeId, address account, address collateralToken, address assetToken, bool isLong) external; 53 | } 54 | -------------------------------------------------------------------------------- /src/interfaces/IGmxRouter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity 0.8.19; 4 | 5 | interface IGmxRouter { 6 | function approvedPlugins(address, address) external view returns (bool); 7 | 8 | function approvePlugin(address _plugin) external; 9 | 10 | function denyPlugin(address _plugin) external; 11 | } -------------------------------------------------------------------------------- /src/interfaces/IGmxVault.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.19; 3 | 4 | interface IGmxVault { 5 | struct Position { 6 | uint256 sizeUsd; 7 | uint256 collateralUsd; 8 | uint256 averagePrice; 9 | uint256 entryFundingRate; 10 | uint256 reserveAmount; 11 | int256 realisedPnlUsd; 12 | uint256 lastIncreasedTime; 13 | } 14 | 15 | event BuyUSDG(address account, address token, uint256 tokenAmount, uint256 usdgAmount, uint256 feeBasisPoints); 16 | event SellUSDG(address account, address token, uint256 usdgAmount, uint256 tokenAmount, uint256 feeBasisPoints); 17 | event Swap( 18 | address account, 19 | address tokenIn, 20 | address tokenOut, 21 | uint256 amountIn, 22 | uint256 amountOut, 23 | uint256 amountOutAfterFees, 24 | uint256 feeBasisPoints 25 | ); 26 | 27 | event IncreasePosition( 28 | bytes32 key, 29 | address account, 30 | address collateralToken, 31 | address indexToken, 32 | uint256 collateralDelta, 33 | uint256 sizeDelta, 34 | bool isLong, 35 | uint256 price, 36 | uint256 fee 37 | ); 38 | event DecreasePosition( 39 | bytes32 key, 40 | address account, 41 | address collateralToken, 42 | address indexToken, 43 | uint256 collateralDelta, 44 | uint256 sizeDelta, 45 | bool isLong, 46 | uint256 price, 47 | uint256 fee 48 | ); 49 | event LiquidatePosition( 50 | bytes32 key, 51 | address account, 52 | address collateralToken, 53 | address indexToken, 54 | bool isLong, 55 | uint256 size, 56 | uint256 collateral, 57 | uint256 reserveAmount, 58 | int256 realisedPnl, 59 | uint256 markPrice 60 | ); 61 | event UpdatePosition( 62 | bytes32 key, 63 | uint256 size, 64 | uint256 collateral, 65 | uint256 averagePrice, 66 | uint256 entryFundingRate, 67 | uint256 reserveAmount, 68 | int256 realisedPnl 69 | ); 70 | event ClosePosition( 71 | bytes32 key, 72 | uint256 size, 73 | uint256 collateral, 74 | uint256 averagePrice, 75 | uint256 entryFundingRate, 76 | uint256 reserveAmount, 77 | int256 realisedPnl 78 | ); 79 | 80 | event UpdateFundingRate(address token, uint256 fundingRate); 81 | event UpdatePnl(bytes32 key, bool hasProfit, uint256 delta); 82 | 83 | event CollectSwapFees(address token, uint256 feeUsd, uint256 feeTokens); 84 | event CollectMarginFees(address token, uint256 feeUsd, uint256 feeTokens); 85 | 86 | event DirectPoolDeposit(address token, uint256 amount); 87 | event IncreasePoolAmount(address token, uint256 amount); 88 | event DecreasePoolAmount(address token, uint256 amount); 89 | event IncreaseUsdgAmount(address token, uint256 amount); 90 | event DecreaseUsdgAmount(address token, uint256 amount); 91 | event IncreaseReservedAmount(address token, uint256 amount); 92 | event DecreaseReservedAmount(address token, uint256 amount); 93 | event IncreaseGuaranteedUsd(address token, uint256 amount); 94 | event DecreaseGuaranteedUsd(address token, uint256 amount); 95 | 96 | function swap( 97 | address _tokenIn, 98 | address _tokenOut, 99 | address _receiver 100 | ) external returns (uint256); 101 | 102 | function getPositionDelta( 103 | address _account, 104 | address _collateralToken, 105 | address _indexToken, 106 | bool _isLong 107 | ) external view returns (bool, uint256); 108 | 109 | function getDelta( 110 | address _indexToken, 111 | uint256 _size, 112 | uint256 _averagePrice, 113 | bool _isLong, 114 | uint256 _lastIncreasedTime 115 | ) external view returns (bool, uint256); 116 | 117 | function getFundingFee( 118 | address _token, 119 | uint256 _size, 120 | uint256 _entryFundingRate 121 | ) external view returns (uint256); 122 | 123 | function getMaxPrice(address _token) external view returns (uint256); 124 | 125 | function getMinPrice(address _token) external view returns (uint256); 126 | 127 | function getNextAveragePrice( 128 | address _indexToken, 129 | uint256 _size, 130 | uint256 _averagePrice, 131 | bool _isLong, 132 | uint256 _nextPrice, 133 | uint256 _sizeDelta, 134 | uint256 _lastIncreasedTime 135 | ) external view returns (uint256); 136 | 137 | function positions(bytes32 key) external view returns (Position memory); 138 | 139 | /** 140 | * [0] size, 141 | * [1] collateral, 142 | * [2] averagePrice, 143 | * [3] entryFundingRate, 144 | * [4] reserveAmount, 145 | * [5] realisedPnl, 146 | * [6] realisedPnl >= 0, 147 | * [7] lastIncreasedTime 148 | */ 149 | function getPosition( 150 | address account, 151 | address collateralToken, 152 | address indexToken, 153 | bool isLong 154 | ) 155 | external 156 | view 157 | returns ( 158 | uint256 size, 159 | uint256 collateral, 160 | uint256 averagePrice, 161 | uint256 entryFundingRate, 162 | uint256 reserveAmount, 163 | uint256 realisedPnl, 164 | bool hasRealisedPnl, 165 | uint256 lastIncreasedTime 166 | ); 167 | 168 | function liquidatePosition( 169 | address _account, 170 | address _collateralToken, 171 | address _indexToken, 172 | bool _isLong, 173 | address _feeReceiver 174 | ) external; 175 | 176 | function usdgAmounts(address) external view returns (uint256); 177 | 178 | function tokenWeights(address) external view returns (uint256); 179 | 180 | function totalTokenWeights() external view returns (uint256); 181 | 182 | function liquidationFeeUsd() external view returns (uint256); 183 | 184 | function taxBasisPoints() external view returns (uint256); 185 | 186 | function stableTaxBasisPoints() external view returns (uint256); 187 | 188 | function swapFeeBasisPoints() external view returns (uint256); 189 | 190 | function stableSwapFeeBasisPoints() external view returns (uint256); 191 | 192 | function marginFeeBasisPoints() external view returns (uint256); 193 | 194 | function priceFeed() external view returns (address); 195 | 196 | function poolAmounts(address token) external view returns (uint256); 197 | 198 | function bufferAmounts(address token) external view returns (uint256); 199 | 200 | function reservedAmounts(address token) external view returns (uint256); 201 | 202 | function getRedemptionAmount(address token, uint256 usdgAmount) external view returns (uint256); 203 | 204 | function minProfitTime() external view returns (uint256); 205 | 206 | function minProfitBasisPoints(address token) external view returns (uint256); 207 | 208 | function maxUsdgAmounts(address token) external view returns (uint256); 209 | 210 | function globalShortSizes(address token) external view returns (uint256); 211 | 212 | function maxGlobalShortSizes(address token) external view returns (uint256); 213 | 214 | function guaranteedUsd(address token) external view returns (uint256); 215 | 216 | function stableTokens(address token) external view returns (bool); 217 | 218 | function fundingRateFactor() external view returns (uint256); 219 | 220 | function stableFundingRateFactor() external view returns (uint256); 221 | 222 | function cumulativeFundingRates(address token) external view returns (uint256); 223 | 224 | function getNextFundingRate(address token) external view returns (uint256); 225 | 226 | function getEntryFundingRate( 227 | address _collateralToken, 228 | address _indexToken, 229 | bool _isLong 230 | ) external view returns (uint256); 231 | 232 | function gov() external view returns (address); 233 | 234 | function setLiquidator(address, bool) external; 235 | } -------------------------------------------------------------------------------- /src/interfaces/IMuxAggregator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity 0.8.19; 3 | 4 | import "../aggregators/mux/Types.sol"; 5 | 6 | interface IMuxAggregator { 7 | 8 | function initialize( 9 | uint256 exchangeId, 10 | address account, 11 | address collateralToken, 12 | uint8 collateralId, 13 | uint8 assetId, 14 | bool isLong 15 | ) external; 16 | 17 | function accountState() external returns(AccountState memory); 18 | 19 | function getSubAccountId() external view returns(bytes32); 20 | 21 | function placePositionOrder( 22 | uint96 collateralAmount, // tokenIn.decimals 23 | uint96 size, // 1e18 24 | uint96 price, // 1e18 25 | uint8 flags, // MARKET, TRIGGER 26 | uint96 assetPrice, // 1e18 27 | uint96 collateralPrice, // 1e18 28 | uint32 deadline, 29 | bool isLong, 30 | uint8 profitTokenId, 31 | address profitTokenAddress, 32 | PositionOrderExtra memory extra 33 | ) external payable; 34 | 35 | function withdraw() external; 36 | 37 | function withdraw_tokens(address tokenAddress) external; 38 | 39 | function cancelOrders(uint64[] calldata keys) external; 40 | 41 | function getPendingOrderKeys() external view returns (uint64[] memory); 42 | } -------------------------------------------------------------------------------- /src/interfaces/IMuxGetter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.19; 3 | 4 | import "../aggregators/mux/Types.sol"; 5 | 6 | interface IMuxGetter { 7 | function getAssetInfo(uint8 assetId) external view returns ( 8 | Asset memory 9 | ); 10 | 11 | function getAllAssetInfo() external view returns ( 12 | address[] memory tokenAddresses, 13 | uint64[] memory prices, 14 | uint32[] memory priceDecimals, 15 | uint32[] memory priceUpdateTimes, 16 | uint32[] memory collateralDecimals, 17 | uint32[] memory interestIndices, 18 | uint32[] memory lastInterestTimes, 19 | uint32[] memory fundingIntervals, 20 | uint96[] memory minTradeSizes, 21 | uint96[] memory maxTradeSizes, 22 | uint96[] memory maxLeverages, 23 | uint96[] memory impactCostFactors, 24 | uint96[] memory fundingRateCoefficients, 25 | uint96[] memory priceDeviationLimits 26 | ); 27 | 28 | function getAssetAddress(uint8 assetId) external view returns (address); 29 | 30 | function getLiquidityPoolStorage() external view returns ( 31 | uint32[8] memory u32s, 32 | uint96[2] memory u96s 33 | ); 34 | 35 | function getSubAccount( 36 | bytes32 subAccountId 37 | ) external view returns (uint96 collateral, uint96 size, uint32 lastIncreasedTime, uint96 entryPrice, uint128 entryFunding); 38 | } 39 | -------------------------------------------------------------------------------- /src/interfaces/IMuxOrderBook.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.19; 3 | 4 | import "../aggregators/mux/Types.sol"; 5 | 6 | interface IMuxOrderBook { 7 | 8 | enum OrderType { 9 | None, // 0 10 | PositionOrder, // 1 11 | LiquidityOrder, // 2 12 | WithdrawalOrder, // 3 13 | RebalanceOrder // 4 14 | } 15 | 16 | event NewPositionOrder( 17 | bytes32 indexed subAccountId, 18 | uint64 indexed orderId, 19 | uint96 collateral, // erc20.decimals 20 | uint96 size, // 1e18 21 | uint96 price, // 1e18 22 | uint8 profitTokenId, 23 | uint8 flags, 24 | uint32 deadline // 1e0. 0 if market order. > 0 if limit order 25 | ); 26 | event NewLiquidityOrder( 27 | address indexed account, 28 | uint64 indexed orderId, 29 | uint8 assetId, 30 | uint96 rawAmount, // erc20.decimals 31 | bool isAdding 32 | ); 33 | event NewWithdrawalOrder( 34 | bytes32 indexed subAccountId, 35 | uint64 indexed orderId, 36 | uint96 rawAmount, // erc20.decimals 37 | uint8 profitTokenId, 38 | bool isProfit 39 | ); 40 | event NewRebalanceOrder( 41 | address indexed rebalancer, 42 | uint64 indexed orderId, 43 | uint8 tokenId0, 44 | uint8 tokenId1, 45 | uint96 rawAmount0, 46 | uint96 maxRawAmount1, 47 | bytes32 userData 48 | ); 49 | event FillOrder(uint64 orderId, OrderType orderType, bytes32[3] orderData); 50 | event CancelOrder(uint64 orderId, OrderType orderType, bytes32[3] orderData); 51 | 52 | function getOrderCount() external view returns (uint256); 53 | function getOrder(uint64 orderId) external view returns (bytes32[3] memory, bool); 54 | function getOrders( 55 | uint256 begin, 56 | uint256 end 57 | ) external view returns (bytes32[3][] memory orderArray, uint256 totalCount); 58 | function placePositionOrder2( 59 | bytes32 subAccountId, 60 | uint96 collateralAmount, // erc20.decimals 61 | uint96 size, // 1e18 62 | uint96 price, // 1e18 63 | uint8 profitTokenId, 64 | uint8 flags, 65 | uint32 deadline, // 1e0 66 | bytes32 referralCode 67 | ) external payable; 68 | function placePositionOrder3( 69 | bytes32 subAccountId, 70 | uint96 collateralAmount, // erc20.decimals 71 | uint96 size, // 1e18 72 | uint96 price, // 1e18 73 | uint8 profitTokenId, 74 | uint8 flags, 75 | uint32 deadline, // 1e0 76 | bytes32 referralCode, 77 | PositionOrderExtra memory extra 78 | ) external payable; 79 | function placeLiquidityOrder( 80 | uint8 assetId, 81 | uint96 rawAmount, // erc20.decimals 82 | bool isAdding 83 | ) external payable; 84 | function placeWithdrawalOrder( 85 | bytes32 subAccountId, 86 | uint96 rawAmount, // erc20.decimals 87 | uint8 profitTokenId, 88 | bool isProfit 89 | ) external; 90 | function cancelOrder(uint64 orderId) external; 91 | function withdrawAllCollateral(bytes32 subAccountId) external; 92 | function depositCollateral(bytes32 subAccountId, uint256 collateralAmount) external payable; 93 | function redeemMuxToken(uint8 tokenId, uint96 muxTokenAmount) external; 94 | 95 | } -------------------------------------------------------------------------------- /src/interfaces/IMuxProxyFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity 0.8.19; 3 | 4 | import "../../lib/openzeppelin-contracts/contracts/proxy/beacon/IBeacon.sol"; 5 | import "../../lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol"; 6 | import "../../lib/openzeppelin-contracts-upgradeable/contracts/token/ERC20/IERC20Upgradeable.sol"; 7 | import "../interfaces/IMuxAggregator.sol"; 8 | 9 | interface IMuxProxyFactory { 10 | 11 | struct PositionArgs { 12 | uint256 exchangeId; 13 | address collateralToken; 14 | uint8 collateralId; 15 | uint8 assetId; 16 | uint8 profitTokenId; 17 | address profitTokenAddress; 18 | bool isLong; 19 | uint96 collateralAmount; // tokenIn.decimals 20 | uint96 size; // 1e18 21 | uint96 price; // 1e18 22 | uint96 collateralPrice; 23 | uint96 assetPrice; 24 | uint8 flags; // MARKET, TRIGGER 25 | bytes32 referralCode; 26 | uint32 deadline; 27 | } 28 | 29 | event SetReferralCode(bytes32 referralCode); 30 | event SetMaintainer(address maintainer, bool enable); 31 | 32 | function initialize(address weth_) external; 33 | 34 | function weth() external view returns (address); 35 | 36 | function implementation() external view returns(address); 37 | 38 | function getImplementationAddress(uint256 exchangeId) external view returns(address); 39 | 40 | function getProxyExchangeId(address proxy) external view returns(uint256); 41 | 42 | function getTradingProxy(bytes32 proxyId) external view returns(address); 43 | 44 | function getExchangeConfig(uint256 ExchangeId) external view returns (uint256[] memory); 45 | 46 | function getExchangeAssetConfig(uint256 ExchangeId, address assetToken) external view returns (uint256[] memory); 47 | 48 | function getMainatinerStatus(address maintainer) external view returns(bool); 49 | 50 | function getConfigVersions(uint256 ExchangeId) external view returns (uint32 exchangeConfigVersion); 51 | 52 | function upgradeTo(uint256 exchangeId, address newImplementation_) external; 53 | 54 | function setExchangeConfig(uint256 ExchangeId, uint256[] memory values) external; 55 | 56 | function setExchangeAssetConfig(uint256 ExchangeId, address assetToken, uint256[] memory values) external; 57 | 58 | function setMaintainer(address maintainer, bool enable) external; 59 | 60 | function setAggregationFee(uint256 fee, bool openAggregationFee, address feeCollector) external; 61 | 62 | function getAggregationFee() external view returns (uint256); 63 | 64 | function getOpenAggregationFeeStatus() external view returns(bool); 65 | 66 | function getFeeCollectorAddress() external view returns(address payable); 67 | 68 | function createProxy(uint256 exchangeId, address collateralToken, address assetToken, address profitToken, bool isLong) external returns (address); 69 | 70 | function openPosition(PositionArgs calldata args, PositionOrderExtra memory extra) external payable; 71 | 72 | function closePosition(PositionArgs calldata args, PositionOrderExtra memory extra) external payable; 73 | 74 | function cancelOrders(uint256 exchangeId, address collateralToken, address assetToken, bool isLong, bytes32[] calldata keys) external; 75 | 76 | function getPendingOrderKeys(uint256 exchangeId, address collateralToken, address assetToken, bool isLong) external view returns(uint64[] memory); 77 | 78 | function withdraw(uint256 exchangeId, address account, address collateralToken, uint8 collateralId, uint8 assetId, bool isLong) external; 79 | } 80 | -------------------------------------------------------------------------------- /src/interfaces/ITransparentUpgradeableProxy.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity 0.8.19; 3 | 4 | import "../../lib/openzeppelin-contracts/contracts/interfaces/IERC1967.sol"; 5 | 6 | interface ITransparentUpgradeableProxy is IERC1967 { 7 | function admin() external view returns (address); 8 | 9 | function implementation() external view returns (address); 10 | 11 | function changeAdmin(address) external; 12 | 13 | function upgradeTo(address) external; 14 | 15 | function upgradeToAndCall(address, bytes memory) external payable; 16 | } -------------------------------------------------------------------------------- /src/interfaces/IWETH.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | 3 | pragma solidity 0.8.19; 4 | 5 | interface IWETH { 6 | function deposit() external payable; 7 | 8 | function transfer(address to, uint256 value) external returns (bool); 9 | 10 | function withdraw(uint256) external; 11 | } -------------------------------------------------------------------------------- /src/muxProxyFactory/MuxProxyBeacon.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity 0.8.19; 3 | 4 | import "../../lib/openzeppelin-contracts/contracts/proxy/beacon/IBeacon.sol"; 5 | import "../../lib/openzeppelin-contracts/contracts/proxy/beacon/BeaconProxy.sol"; 6 | 7 | import "./MuxStorage.sol"; 8 | 9 | contract MuxProxyBeacon is MuxStorage, IBeacon{ 10 | event Upgraded(uint256 exchangeId, address indexed implementation); 11 | event CreateProxy( 12 | uint256 exchangeId, 13 | bytes32 proxyId, 14 | address owner, 15 | address proxy, 16 | uint8 assetId, 17 | uint8 collateralId, 18 | bool isLong 19 | ); 20 | 21 | function implementation() external view virtual override returns (address) { 22 | require(_isCreatedProxy(msg.sender), "NotProxy"); 23 | return _implementations[_proxyExchangeIds[msg.sender]]; 24 | } 25 | 26 | function _isCreatedProxy(address proxy_) internal view returns (bool) { 27 | return _proxyExchangeIds[proxy_] != 0; 28 | } 29 | 30 | function _setImplementation(uint256 exchangeId, address newImplementation_) internal { 31 | require(newImplementation_ != address(0), "ZeroImplementationAddress"); 32 | _implementations[exchangeId] = newImplementation_; 33 | } 34 | 35 | function _upgradeTo(uint256 exchangeId, address newImplementation_) internal virtual { 36 | _setImplementation(exchangeId, newImplementation_); 37 | emit Upgraded(exchangeId, newImplementation_); 38 | } 39 | 40 | function _createProxy( 41 | uint256 exchangeId, 42 | bytes32 proxyId, 43 | bytes memory bytecode 44 | ) internal returns (address proxy) { 45 | proxy = _getAddress(bytecode, proxyId); 46 | _proxyExchangeIds[proxy] = exchangeId; // IMPORTANT 47 | assembly { 48 | proxy := create2(0x0, add(0x20, bytecode), mload(bytecode), proxyId) 49 | } 50 | require(proxy != address(0), "CreateFailed"); 51 | } 52 | 53 | function _createBeaconProxy( 54 | uint256 exchangeId, 55 | address account, 56 | address collateralToken, 57 | uint8 assetId, 58 | uint8 collateralId, 59 | bool isLong 60 | ) internal returns (address) { 61 | bytes32 proxyId = _makeProxyId(exchangeId, account, collateralToken, collateralId, assetId, isLong); 62 | require(_tradingProxies[proxyId] == address(0), "AlreadyCreated"); 63 | bytes memory initData = abi.encodeWithSignature( 64 | "initialize(uint256,address,address,uint8,uint8,bool)", 65 | exchangeId, 66 | account, 67 | collateralToken, 68 | collateralId, 69 | assetId, 70 | isLong 71 | ); 72 | bytes memory bytecode = abi.encodePacked(type(BeaconProxy).creationCode, abi.encode(address(this), initData)); 73 | address proxy = _createProxy(exchangeId, proxyId, bytecode); 74 | _tradingProxies[proxyId] = proxy; 75 | _ownedProxies[account].push(proxy); 76 | emit CreateProxy(exchangeId, proxyId, account, proxy, assetId, collateralId, isLong); 77 | return proxy; 78 | } 79 | 80 | function _getAddress(bytes memory bytecode, bytes32 proxyId) internal view returns (address) { 81 | bytes32 hash = keccak256(abi.encodePacked(bytes1(0xff), address(this), proxyId, keccak256(bytecode))); 82 | return address(uint160(uint256(hash))); 83 | } 84 | 85 | function _makeProxyId( 86 | uint256 exchangeId_, 87 | address account_, 88 | address collateralToken, 89 | uint8 collateralId_, 90 | uint8 assetId_, 91 | bool isLong_ 92 | ) internal pure returns (bytes32) { 93 | return keccak256(abi.encodePacked(exchangeId_, account_, collateralToken,collateralId_, assetId_, isLong_)); 94 | } 95 | } -------------------------------------------------------------------------------- /src/muxProxyFactory/MuxProxyConfig.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity 0.8.19; 3 | 4 | import "./MuxStorage.sol"; 5 | 6 | contract MuxProxyConfig is MuxStorage{ 7 | 8 | event SetExchangeConfig(uint256 ExchangeId, uint256[] values, uint256 version); 9 | 10 | function _getLatestVersions(uint256 ExchangeId) 11 | internal 12 | view 13 | returns (uint32 ExchangeConfigVersion) 14 | { 15 | ExchangeConfigVersion = _exchangeConfigs[ExchangeId].version; 16 | } 17 | 18 | function _setExchangeConfig(uint256 ExchangeId, uint256[] memory values) internal { 19 | _exchangeConfigs[ExchangeId].values = values; 20 | _exchangeConfigs[ExchangeId].version += 1; 21 | emit SetExchangeConfig(ExchangeId, values, _exchangeConfigs[ExchangeId].version); 22 | } 23 | } -------------------------------------------------------------------------------- /src/muxProxyFactory/MuxProxyFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity 0.8.19; 3 | 4 | import "../../lib/openzeppelin-contracts/contracts/proxy/beacon/BeaconProxy.sol"; 5 | import "../../lib/openzeppelin-contracts/contracts/proxy/beacon/IBeacon.sol"; 6 | 7 | import "../../lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol"; 8 | import "../../lib/openzeppelin-contracts-upgradeable/contracts/token/ERC20/IERC20Upgradeable.sol"; 9 | import "../../lib/openzeppelin-contracts-upgradeable/contracts/token/ERC20/utils/SafeERC20Upgradeable.sol"; 10 | 11 | import "../interfaces/IMuxAggregator.sol"; 12 | 13 | import "./MuxStorage.sol"; 14 | import "./MuxProxyBeacon.sol"; 15 | import "./MuxProxyConfig.sol"; 16 | 17 | contract MuxProxyFactory is MuxStorage, MuxProxyBeacon, MuxProxyConfig, OwnableUpgradeable { 18 | using SafeERC20Upgradeable for IERC20Upgradeable; 19 | 20 | struct PositionArgs { 21 | uint256 exchangeId; 22 | address collateralToken; 23 | uint8 collateralId; 24 | uint8 assetId; 25 | uint8 profitTokenId; 26 | address profitTokenAddress; 27 | bool isLong; 28 | uint96 collateralAmount; // tokenIn.decimals 29 | uint96 size; // 1e18 30 | uint96 price; // 1e18 31 | uint96 collateralPrice; 32 | uint96 assetPrice; 33 | uint8 flags; // MARKET, TRIGGER 34 | bytes32 referralCode; 35 | uint32 deadline; 36 | } 37 | 38 | event SetReferralCode(bytes32 referralCode); 39 | event SetMaintainer(address maintainer, bool enable); 40 | 41 | function initialize(address weth_) external initializer { 42 | __Ownable_init(); 43 | _weth = weth_; 44 | } 45 | 46 | function weth() external view returns (address) { 47 | return _weth; 48 | } 49 | 50 | // ======================== getter methods ======================== 51 | 52 | function getImplementationAddress(uint256 exchangeId) external view returns(address){ 53 | return _implementations[exchangeId]; 54 | } 55 | 56 | function getProxyExchangeId(address proxy) external view returns(uint256){ 57 | return _proxyExchangeIds[proxy]; 58 | } 59 | 60 | function getTradingProxy(bytes32 proxyId) external view returns(address){ 61 | return _tradingProxies[proxyId]; 62 | } 63 | 64 | function getProxiesOf(address account) public view returns (address[] memory) { 65 | return _ownedProxies[account]; 66 | } 67 | 68 | function getExchangeConfig(uint256 ExchangeId) external view returns (uint256[] memory) { 69 | return _exchangeConfigs[ExchangeId].values; 70 | } 71 | 72 | function getMainatinerStatus(address maintainer) external view returns(bool){ 73 | return _maintainers[maintainer]; 74 | } 75 | 76 | function getConfigVersions(uint256 ExchangeId) 77 | external 78 | view 79 | returns (uint32 ExchangeConfigVersion) 80 | { 81 | return _getLatestVersions(ExchangeId); 82 | } 83 | 84 | // ======================== methods for contract management ======================== 85 | function upgradeTo(uint256 exchangeId, address newImplementation_) external onlyOwner { 86 | _upgradeTo(exchangeId, newImplementation_); 87 | } 88 | 89 | function setExchangeConfig(uint256 ExchangeId, uint256[] memory values) external { 90 | require(_maintainers[msg.sender] || msg.sender == owner(), "OnlyMaintainerOrAbove"); 91 | _setExchangeConfig(ExchangeId, values); 92 | } 93 | 94 | function setMaintainer(address maintainer, bool enable) external onlyOwner { 95 | _maintainers[maintainer] = enable; 96 | emit SetMaintainer(maintainer, enable); 97 | } 98 | 99 | function setAggregationFee(uint96 fee, bool openAggregationFee, address feeCollector) external onlyOwner { 100 | require(fee <= 10000, "FeeTooHigh"); // This ensures that the fee can't be set above 100% 101 | require(feeCollector != address(0), "InvalidFeeCollector"); // Ensure feeCollector is not the zero address 102 | _aggregationFee = fee; 103 | _openAggregationFee = openAggregationFee; 104 | _feeCollector = payable(feeCollector); 105 | } 106 | 107 | function getAggregationFee() external view returns (uint96){ 108 | return _aggregationFee; 109 | } 110 | 111 | function getOpenAggregationFeeStatus() external view returns(bool){ 112 | return _openAggregationFee; 113 | } 114 | 115 | function getFeeCollectorAddress() external view returns(address payable){ 116 | return _feeCollector; 117 | } 118 | 119 | // ======================== methods called by user ======================== 120 | function createProxy( 121 | uint256 exchangeId, 122 | address collateralToken, 123 | uint8 collateralId, 124 | uint8 assetId, 125 | bool isLong 126 | ) public returns (address) { 127 | return 128 | _createBeaconProxy( 129 | exchangeId, 130 | msg.sender, 131 | collateralToken, 132 | assetId, 133 | collateralId, 134 | isLong 135 | ); 136 | } 137 | 138 | function openPosition(PositionArgs calldata args, PositionOrderExtra calldata extra) external payable { 139 | address proxy = _tradingProxies[_makeProxyId(args.exchangeId, msg.sender, args.collateralToken, args.collateralId, args.assetId, args.isLong)]; 140 | if (proxy == address(0)) { 141 | proxy = createProxy(args.exchangeId, args.collateralToken, args.collateralId, args.assetId, args.isLong); 142 | } 143 | 144 | uint96 collateralAfterFee = _handleFee(args.collateralToken, args.collateralAmount, _openAggregationFee); 145 | 146 | if (args.collateralToken != _weth) { 147 | IERC20Upgradeable(args.collateralToken).safeTransferFrom(msg.sender, proxy, collateralAfterFee); 148 | IMuxAggregator(proxy).placePositionOrder{ value: msg.value }( 149 | collateralAfterFee, 150 | args.size, 151 | args.price, 152 | args.flags, 153 | args.assetPrice, 154 | args.collateralPrice, 155 | args.deadline, 156 | args.isLong, 157 | args.profitTokenId, 158 | args.profitTokenAddress, 159 | extra 160 | ); 161 | } else { 162 | require(msg.value >= collateralAfterFee, "InsufficientAmountIn"); 163 | IMuxAggregator(proxy).placePositionOrder{ value: collateralAfterFee }( 164 | collateralAfterFee, 165 | args.size, 166 | args.price, 167 | args.flags, 168 | args.assetPrice, 169 | args.collateralPrice, 170 | args.deadline, 171 | args.isLong, 172 | args.profitTokenId, 173 | args.profitTokenAddress, 174 | extra 175 | ); 176 | } 177 | } 178 | 179 | 180 | function closePosition(PositionArgs calldata args, PositionOrderExtra calldata extra) external payable { 181 | address proxy = _mustGetProxy(args.exchangeId, msg.sender, args.collateralToken, args.collateralId, args.assetId, args.isLong); 182 | 183 | IMuxAggregator(proxy).placePositionOrder{ value: msg.value }( 184 | args.collateralAmount, 185 | args.size, 186 | args.price, 187 | args.flags, 188 | args.assetPrice, 189 | args.collateralPrice, 190 | args.deadline, 191 | args.isLong, 192 | args.profitTokenId, 193 | args.profitTokenAddress, 194 | extra 195 | ); 196 | } 197 | 198 | function cancelOrders( 199 | uint256 exchangeId, 200 | address collateralToken, 201 | uint8 collateralId, 202 | uint8 assetId, 203 | bool isLong, 204 | uint64[] calldata keys 205 | ) external { 206 | IMuxAggregator(_mustGetProxy(exchangeId, msg.sender, collateralToken, collateralId, assetId, isLong)).cancelOrders(keys); 207 | } 208 | 209 | function getPendingOrderKeys(uint256 exchangeId, address collateralToken, uint8 collateralId, uint8 assetId, bool isLong) external view returns(uint64[] memory){ 210 | return IMuxAggregator(_mustGetProxy(exchangeId, msg.sender, collateralToken, collateralId, assetId, isLong)).getPendingOrderKeys(); 211 | } 212 | 213 | function withdraw( 214 | uint256 exchangeId, 215 | address account, 216 | address collateralToken, 217 | uint8 collateralId, 218 | uint8 assetId, 219 | bool isLong 220 | ) external { 221 | require(_maintainers[msg.sender] || msg.sender == owner(), "OnlyMaintainerOrAbove"); 222 | IMuxAggregator(_mustGetProxy(exchangeId, account, collateralToken, collateralId, assetId, isLong)).withdraw(); 223 | } 224 | 225 | function withdraw_tokens( 226 | uint256 exchangeId, 227 | address account, 228 | address collateralToken, 229 | uint8 collateralId, 230 | uint8 assetId, 231 | bool isLong, 232 | address withdrawToken 233 | ) external { 234 | require(_maintainers[msg.sender] || msg.sender == owner(), "OnlyMaintainerOrAbove"); 235 | IMuxAggregator(_mustGetProxy(exchangeId, account, collateralToken, collateralId, assetId, isLong)).withdraw_tokens(withdrawToken); 236 | } 237 | 238 | function removeProxy(bytes32 proxyId, address userAddress) external onlyOwner { 239 | // Fetch the proxy address from _tradingProxies 240 | address proxyAddress = _tradingProxies[proxyId]; 241 | 242 | // Delete the entry in _tradingProxies 243 | delete _tradingProxies[proxyId]; 244 | 245 | // Delete the entry in _proxyExchangeIds 246 | delete _proxyExchangeIds[proxyAddress]; 247 | 248 | // Remove the proxyAddress from the _ownedProxies array for the userAddress 249 | address[] storage ownedProxies = _ownedProxies[userAddress]; 250 | for (uint i = 0; i < ownedProxies.length; i++) { 251 | if (ownedProxies[i] == proxyAddress) { 252 | // We found the proxyAddress, now we need to remove it 253 | ownedProxies[i] = ownedProxies[ownedProxies.length - 1]; 254 | ownedProxies.pop(); 255 | break; 256 | } 257 | } 258 | } 259 | 260 | // ======================== Utility methods ======================== 261 | function _mustGetProxy( 262 | uint256 exchangeId, 263 | address account, 264 | address collateralToken, 265 | uint8 collateralId, 266 | uint8 assetId, 267 | bool isLong 268 | ) internal view returns (address proxy) { 269 | bytes32 proxyId = _makeProxyId(exchangeId, account, collateralToken, collateralId, assetId, isLong); 270 | proxy = _tradingProxies[proxyId]; 271 | require(proxy != address(0), "ProxyNotExist"); 272 | } 273 | 274 | function _handleFee(address collateralToken, uint96 collateralAmount, bool openAggregationFee) internal returns (uint96 collateralAfterFee) { 275 | uint96 feeAmount = collateralAmount * _aggregationFee / 10000; 276 | collateralAfterFee = collateralAmount - feeAmount; 277 | 278 | if (collateralToken != _weth) { 279 | if (feeAmount > 0 && openAggregationFee) { 280 | IERC20Upgradeable(collateralToken).safeTransferFrom(msg.sender, _feeCollector, feeAmount); 281 | } 282 | } else { 283 | if (feeAmount > 0 && openAggregationFee) { 284 | (bool success, ) = _feeCollector.call{value: feeAmount}(""); 285 | require(success, "Transfer failed"); 286 | } 287 | } 288 | return collateralAfterFee; 289 | } 290 | 291 | } -------------------------------------------------------------------------------- /src/muxProxyFactory/MuxStorage.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity 0.8.19; 3 | 4 | import "../../lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol"; 5 | 6 | contract MuxStorage is Initializable{ 7 | 8 | struct ConfigData { 9 | uint32 version; 10 | uint256[] values; 11 | } 12 | 13 | //mapping exchangeID to its implementation address 14 | mapping(uint256 => address) internal _implementations; 15 | //mapping proxy addresses to their exchangeIds 16 | mapping(address => uint256) internal _proxyExchangeIds; 17 | //mapping proxy IDs to their addresses 18 | mapping(bytes32 => address) internal _tradingProxies; 19 | //mapping user address to proxies owned by the user 20 | mapping(address => address[]) internal _ownedProxies; 21 | //mapping Exchange ID to Exchange Config Data 22 | mapping(uint256 => ConfigData) internal _exchangeConfigs; 23 | 24 | //mapping to identify maintainers of proxy factory 25 | mapping(address => bool) _maintainers; 26 | 27 | //record weth address 28 | address internal _weth; 29 | 30 | //address for logX referral manager 31 | address internal _referralManager; 32 | 33 | //Aggregation Fee variables 34 | uint96 internal _aggregationFee; 35 | bool internal _openAggregationFee; 36 | address payable internal _feeCollector; 37 | } -------------------------------------------------------------------------------- /src/transparentUpgradeableProxy/TransparentUpgradeableProxy.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity 0.8.19; 3 | 4 | import "../../lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol"; 5 | 6 | import "../interfaces/ITransparentUpgradeableProxy.sol"; 7 | 8 | /** 9 | * @dev This contract implements a proxy that is upgradeable by an admin. 10 | * 11 | * To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector 12 | * clashing], which can potentially be used in an attack, this contract uses the 13 | * https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two 14 | * things that go hand in hand: 15 | * 16 | * 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if 17 | * that call matches one of the admin functions exposed by the proxy itself. 18 | * 2. If the admin calls the proxy, it can access the admin functions, but its calls will never be forwarded to the 19 | * implementation. If the admin tries to call a function on the implementation it will fail with an error that says 20 | * "admin cannot fallback to proxy target". 21 | * 22 | * These properties mean that the admin account can only be used for admin actions like upgrading the proxy or changing 23 | * the admin, so it's best if it's a dedicated account that is not used for anything else. This will avoid headaches due 24 | * to sudden errors when trying to call a function from the proxy implementation. 25 | * 26 | * Our recommendation is for the dedicated account to be an instance of the {ProxyAdmin} contract. If set up this way, 27 | * you should think of the `ProxyAdmin` instance as the real administrative interface of your proxy. 28 | * 29 | * NOTE: The real interface of this proxy is that defined in `ITransparentUpgradeableProxy`. This contract does not 30 | * inherit from that interface, and instead the admin functions are implicitly implemented using a custom dispatch 31 | * mechanism in `_fallback`. Consequently, the compiler will not produce an ABI for this contract. This is necessary to 32 | * fully implement transparency without decoding reverts caused by selector clashes between the proxy and the 33 | * implementation. 34 | * 35 | * WARNING: It is not recommended to extend this contract to add additional external functions. If you do so, the compiler 36 | * will not check that there are no selector conflicts, due to the note above. A selector clash between any new function 37 | * and the functions declared in {ITransparentUpgradeableProxy} will be resolved in favor of the new one. This could 38 | * render the admin operations inaccessible, which could prevent upgradeability. Transparency may also be compromised. 39 | */ 40 | contract TransparentUpgradeableProxy is ERC1967Proxy { 41 | /** 42 | * @dev Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and 43 | * optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}. 44 | */ 45 | constructor( 46 | address _logic, 47 | address admin_, 48 | bytes memory _data 49 | ) payable ERC1967Proxy(_logic, _data) { 50 | _changeAdmin(admin_); 51 | } 52 | 53 | /** 54 | * @dev Modifier used internally that will delegate the call to the implementation unless the sender is the admin. 55 | * 56 | * CAUTION: This modifier is deprecated, as it could cause issues if the modified function has arguments, and the 57 | * implementation provides a function with the same selector. 58 | */ 59 | modifier ifAdmin() { 60 | if (msg.sender == _getAdmin()) { 61 | _; 62 | } else { 63 | _fallback(); 64 | } 65 | } 66 | 67 | /** 68 | * @dev If caller is the admin process the call internally, otherwise transparently fallback to the proxy behavior 69 | */ 70 | function _fallback() internal virtual override { 71 | if (msg.sender == _getAdmin()) { 72 | bytes memory ret; 73 | bytes4 selector = msg.sig; 74 | if (selector == ITransparentUpgradeableProxy.upgradeTo.selector) { 75 | ret = _dispatchUpgradeTo(); 76 | } else if (selector == ITransparentUpgradeableProxy.upgradeToAndCall.selector) { 77 | ret = _dispatchUpgradeToAndCall(); 78 | } else if (selector == ITransparentUpgradeableProxy.changeAdmin.selector) { 79 | ret = _dispatchChangeAdmin(); 80 | } else if (selector == ITransparentUpgradeableProxy.admin.selector) { 81 | ret = _dispatchAdmin(); 82 | } else if (selector == ITransparentUpgradeableProxy.implementation.selector) { 83 | ret = _dispatchImplementation(); 84 | } else { 85 | revert("TransparentUpgradeableProxy: admin cannot fallback to proxy target"); 86 | } 87 | assembly { 88 | return(add(ret, 0x20), mload(ret)) 89 | } 90 | } else { 91 | super._fallback(); 92 | } 93 | } 94 | 95 | /** 96 | * @dev Returns the current admin. 97 | * 98 | * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the 99 | * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call. 100 | * `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103` 101 | */ 102 | function _dispatchAdmin() private returns (bytes memory) { 103 | _requireZeroValue(); 104 | 105 | address admin = _getAdmin(); 106 | return abi.encode(admin); 107 | } 108 | 109 | /** 110 | * @dev Returns the current implementation. 111 | * 112 | * TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the 113 | * https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call. 114 | * `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc` 115 | */ 116 | function _dispatchImplementation() private returns (bytes memory) { 117 | _requireZeroValue(); 118 | 119 | address implementation = _implementation(); 120 | return abi.encode(implementation); 121 | } 122 | 123 | /** 124 | * @dev Changes the admin of the proxy. 125 | * 126 | * Emits an {AdminChanged} event. 127 | */ 128 | function _dispatchChangeAdmin() private returns (bytes memory) { 129 | _requireZeroValue(); 130 | 131 | address newAdmin = abi.decode(msg.data[4:], (address)); 132 | _changeAdmin(newAdmin); 133 | 134 | return ""; 135 | } 136 | 137 | /** 138 | * @dev Upgrade the implementation of the proxy. 139 | */ 140 | function _dispatchUpgradeTo() private returns (bytes memory) { 141 | _requireZeroValue(); 142 | 143 | address newImplementation = abi.decode(msg.data[4:], (address)); 144 | _upgradeToAndCall(newImplementation, bytes(""), false); 145 | 146 | return ""; 147 | } 148 | 149 | /** 150 | * @dev Upgrade the implementation of the proxy, and then call a function from the new implementation as specified 151 | * by `data`, which should be an encoded function call. This is useful to initialize new storage variables in the 152 | * proxied contract. 153 | */ 154 | function _dispatchUpgradeToAndCall() private returns (bytes memory) { 155 | (address newImplementation, bytes memory data) = abi.decode(msg.data[4:], (address, bytes)); 156 | _upgradeToAndCall(newImplementation, data, true); 157 | 158 | return ""; 159 | } 160 | 161 | /** 162 | * @dev Returns the current admin. 163 | */ 164 | function _admin() internal view virtual returns (address) { 165 | return _getAdmin(); 166 | } 167 | 168 | /** 169 | * @dev To keep this contract fully transparent, all `ifAdmin` functions must be payable. This helper is here to 170 | * emulate some proxy functions being non-payable while still allowing value to pass through. 171 | */ 172 | function _requireZeroValue() private { 173 | require(msg.value == 0); 174 | } 175 | } -------------------------------------------------------------------------------- /test/test_gmxAdapter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | pragma solidity ^0.8.17; 3 | 4 | import "../lib/forge-std/src/Test.sol"; 5 | 6 | import "../lib/openzeppelin-contracts/contracts/proxy/beacon/BeaconProxy.sol"; 7 | 8 | import "../src/interfaces/IGmxAggregator.sol"; 9 | import "../src/interfaces/IGmxProxyFactory.sol"; 10 | import "../src/interfaces/IGmxRouter.sol"; 11 | import "../src/interfaces/IGmxOrderBook.sol"; 12 | import "../src/interfaces/IGmxPositionRouter.sol"; 13 | import "../src/interfaces/IGmxVault.sol"; 14 | 15 | import "../src/aggregators/gmx/GmxAdapter.sol"; 16 | import "../src/aggregators/gmx/Types.sol"; 17 | import "./test_gmxSetUp.sol"; 18 | 19 | contract TestGmxAdapter is Test, Setup{ 20 | IGmxProxyFactory private _proxyFactory; 21 | IGmxRouter private _gmxRouter; 22 | IGmxOrderBook private _gmxOrderBook; 23 | IGmxPositionRouter private _gmxPositionRouter; 24 | IGmxVault private _gmxVault; 25 | IERC20 private _erc20; 26 | IWETH private _iweth; 27 | 28 | //Initializing two adapters, one for a long position and another for a short position 29 | IGmxAggregator private _gmxAdapterProxyLong; 30 | IGmxAggregator private _gmxAdapterProxyShort; 31 | GMXAdapter private _gmxAdapterInstance; 32 | 33 | event OpenPosition(address collateralToken, address indexToken, bool isLong, OpenPositionContext context); 34 | event ClosePosition(address collateralToken, address indexToken, bool isLong, ClosePositionContext context); 35 | event AddPendingOrder( 36 | LibGmx.OrderCategory category, 37 | LibGmx.OrderReceiver receiver, 38 | uint256 index, 39 | uint256 timestamp 40 | ); 41 | event Withdraw( 42 | address collateralAddress, 43 | address account, 44 | uint256 balance 45 | ); 46 | 47 | function setUp() public { 48 | _gmxAdapterInstance = new GMXAdapter(_weth); 49 | setUpGmxConfig(); 50 | 51 | //For the sake of this testing, the TestGmxAdapter contract will be acting like proxyFactory. Therefore, we mock all the calls made by GmxAdapter to Proxy factory with address(this) 52 | //Mock implementation() call for creating Aggregator contract 53 | vm.mockCall(address(this), abi.encodeWithSelector(_proxyFactory.implementation.selector), abi.encode(_gmxAdapterInstance)); 54 | //Mock call to factory during _updateConfigs() 55 | vm.mockCall(address(this), abi.encodeWithSelector(_proxyFactory.getConfigVersions.selector), abi.encode(1, 1)); 56 | vm.mockCall(address(this), abi.encodeWithSelector(_proxyFactory.getExchangeConfig.selector), abi.encode(gmxExchangeConfigs)); 57 | //Mock transferFrom calls to collateral tokens 58 | vm.mockCall(address(_wbtc), abi.encodeWithSelector(_erc20.transferFrom.selector), abi.encode()); 59 | vm.mockCall(address(_dai), abi.encodeWithSelector(_erc20.transferFrom.selector), abi.encode()); 60 | vm.mockCall(address(_dai), abi.encodeWithSelector(_erc20.transfer.selector), abi.encode()); 61 | vm.mockCall(address(_weth), abi.encodeWithSelector(_erc20.transfer.selector), abi.encode()); 62 | //Mock GMX Vault Swap 63 | vm.mockCall(address(bytes20(bytes32(gmxExchangeConfigs[0]))), abi.encodeWithSelector(_gmxVault.swap.selector), abi.encode(18000000)); 64 | vm.mockCall(address(_weth), abi.encodeWithSelector(_iweth.deposit.selector), abi.encode()); 65 | 66 | // ----------- Long Position Initialization ---------------- 67 | address proxyLong; 68 | //for long position on GMX, the collateral and asset token are the same. 69 | bytes32 proxyIdLong = keccak256(abi.encodePacked(_exchangeId, _account, _wbtc, _wbtc, true)); 70 | bytes memory initDataLong = abi.encodeWithSignature( 71 | "initialize(uint256,address,address,address,bool)", 72 | _exchangeId, 73 | _account, 74 | _wbtc, 75 | _wbtc, 76 | true 77 | ); 78 | bytes memory bytecodeLong = abi.encodePacked(type(BeaconProxy).creationCode, abi.encode(address(this), initDataLong)); 79 | assembly { 80 | proxyLong := create2(0x0, add(0x20, bytecodeLong), mload(bytecodeLong), proxyIdLong) 81 | } 82 | require(proxyLong != address(0), "CreateFailed"); 83 | 84 | _gmxAdapterProxyLong = IGmxAggregator(proxyLong); 85 | 86 | // ----------- Short Position Initialization ---------------- 87 | address proxyShort; 88 | //for short position on GMX, the collateral token is always a stable coin. 89 | bytes32 proxyIdShort = keccak256(abi.encodePacked(_exchangeId, _account, _dai, _weth, false)); 90 | bytes memory initDataShort = abi.encodeWithSignature( 91 | "initialize(uint256,address,address,address,bool)", 92 | _exchangeId, 93 | _account, 94 | _dai, 95 | _weth, 96 | false 97 | ); 98 | bytes memory bytecodeShort = abi.encodePacked(type(BeaconProxy).creationCode, abi.encode(address(this), initDataShort)); 99 | assembly { 100 | proxyShort := create2(0x0, add(0x20, bytecodeShort), mload(bytecodeShort), proxyIdShort) 101 | } 102 | require(proxyShort != address(0), "CreateFailed"); 103 | 104 | _gmxAdapterProxyShort = IGmxAggregator(proxyShort); 105 | } 106 | 107 | function testGmxAdapterInitialization() public{ 108 | AccountState memory currentAccountLong = _gmxAdapterProxyLong.accountState(); 109 | assertEq(currentAccountLong.account, _account); 110 | assertEq(currentAccountLong.collateralToken, _wbtc); 111 | assertEq(currentAccountLong.indexToken, _wbtc); 112 | assertEq(currentAccountLong.account, _account); 113 | assertEq(currentAccountLong.isLong, true); 114 | assertEq(currentAccountLong.collateralDecimals, 8); 115 | 116 | AccountState memory currentAccountShort = _gmxAdapterProxyShort.accountState(); 117 | assertEq(currentAccountShort.account, _account); 118 | assertEq(currentAccountShort.collateralToken, _dai); 119 | assertEq(currentAccountShort.indexToken, _weth); 120 | assertEq(currentAccountShort.account, _account); 121 | assertEq(currentAccountShort.isLong, false); 122 | assertEq(currentAccountShort.collateralDecimals, 18); 123 | } 124 | 125 | function testGmxAdapterOpenPosition() public{ 126 | OpenPositionContext memory openOrderLongContext; 127 | vm.expectEmit(true, true, true, false); 128 | emit OpenPosition(_wbtc, _wbtc, true, openOrderLongContext); 129 | uint8 flags = 0x40; 130 | //Placing a long open market position order with 0.0018 ETH for execution Fees, 18 DAI as collateral, mininum swap out amount 0 on size of $600 worth WBTC 131 | _gmxAdapterProxyLong.openPosition{value:180000000000000}(_dai, 18000000000000000000, 0, 600000000000000000000, 0, 0, 0, flags); 132 | 133 | OpenPositionContext memory openOrderShortContext; 134 | vm.expectEmit(true, true, true, false); 135 | emit OpenPosition(_dai, _weth, false, openOrderShortContext); 136 | //Placing a short open limit position order with 0.0018 ETH, 18 DAI as collateral, mininum swap out amount 0 on size of $600 worth ETH on a limit price of $1900 137 | _gmxAdapterProxyShort.openPosition{value:180000000000000}(_dai, 18000000000000000000, 0, 600000000000000000000, 1900000000000000000000, 0, 0, 0); 138 | } 139 | 140 | function testGmxAdapterClosePosition() public { 141 | ClosePositionContext memory closeOrderLongContext; 142 | vm.expectEmit(true, true, true, false); 143 | emit ClosePosition(_wbtc, _wbtc, true, closeOrderLongContext); 144 | //Placing a long close limit position order with 0.0018 ETH for execution Fees, 18 DAI for collateral on size of $600 worth BTC and limit price of $1900 145 | _gmxAdapterProxyLong.closePosition{value:180000000000000}(18000000000000000000, 600000000000000000000, 1900000000000000000000, 0, 0, 0); 146 | 147 | ClosePositionContext memory closeOrderShortContext; 148 | vm.expectEmit(true, true, true, false); 149 | emit ClosePosition(_dai, _weth, false, closeOrderShortContext); 150 | uint8 flags = 0x40; 151 | //Placing a short close market position order with 0.0018 ETH for execution Fees, 18 DAI for collateral on size of $600 worth ETH 152 | _gmxAdapterProxyShort.closePosition{value:180000000000000}(18000000000000000000, 600000000000000000000, 0, 0, 0, flags); 153 | 154 | //Test Update Orders 155 | bytes32[] memory ordersBefore = _gmxAdapterProxyLong.getPendingOrderKeys(); 156 | uint256 startOrdersLength = ordersBefore.length; 157 | bytes32 orderKey = ordersBefore[0]; 158 | assertEq(startOrdersLength > 0, true, "0 starting Orders"); 159 | 160 | (bool isFilledLong, LibGmx.OrderHistory memory history) = _gmxAdapterProxyLong.getOrder(orderKey); 161 | assertEq(isFilledLong, false); 162 | assertEq(history.receiver == LibGmx.OrderReceiver.OB_DEC, true); 163 | assertEq(history.category == LibGmx.OrderCategory.CLOSE, true); 164 | 165 | _gmxAdapterProxyLong.updateOrder(orderKey, 0, 0, 10000000000000000000, false); 166 | 167 | //Test Cancel Orders 168 | _gmxAdapterProxyLong.cancelOrders(ordersBefore); 169 | bytes32[] memory ordersAfter = _gmxAdapterProxyLong.getPendingOrderKeys(); 170 | uint256 endOrdersLength = ordersAfter.length; 171 | assertEq(endOrdersLength == 0, true, "All Orders not cancelled"); 172 | } 173 | 174 | function testGmxAdapterTPSLOrders() public{ 175 | ClosePositionContext memory closeOrderLongContext; 176 | vm.expectEmit(true, true, true, false); 177 | emit ClosePosition(_wbtc, _wbtc, true, closeOrderLongContext); 178 | //Placing a long close TPSL position order with 0.005 ETH for execution Fees, 18 DAI for collateral on size of $600 worth BTC (0 min swap out amount) and take profit price of $1900 and stop loss price of $1100 179 | _gmxAdapterProxyLong.openPosition{value:500000000000000}(_dai, 18000000000000000000, 0, 600000000000000000000, 0, 1900000000000000000000, 1100000000000000000000, 0x08); 180 | } 181 | 182 | //ToDo - test cancelTimeout Orders 183 | //ToDo - test GmxAdapterGetPositionKey 184 | } -------------------------------------------------------------------------------- /test/test_gmxProxyFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | pragma solidity ^0.8.17; 3 | 4 | import "../lib/forge-std/src/Test.sol"; 5 | import "../src/gmxProxyFactory/GmxProxyFactory.sol"; 6 | import "../lib/openzeppelin-contracts/contracts/proxy/beacon/IBeacon.sol"; 7 | import "./test_gmxSetUp.sol"; 8 | 9 | contract TestProxyFactory is Test, Setup{ 10 | 11 | //Note test for createProxy done in gmxAdapter tests while initializing the contracts. 12 | 13 | GmxProxyFactory private _proxyFactory; 14 | 15 | function setUp() public { 16 | _proxyFactory = new GmxProxyFactory(); 17 | 18 | setUpGmxConfig(); 19 | 20 | _proxyFactory.initialize(_weth); 21 | _proxyFactory.upgradeTo(_exchangeId, _implementation); 22 | 23 | //Set Maintainer 24 | _proxyFactory.setMaintainer(_maintainer, true); 25 | } 26 | 27 | function testGmxInitialize() public{ 28 | assertEq(_proxyFactory.weth(), _weth, "WETH was not correctly initialized"); 29 | assertEq(_proxyFactory.owner(), address(this), "Owner was not correctly initialized"); 30 | } 31 | 32 | function testGmxUpgradeTo() public{ 33 | assertEq(_proxyFactory.getImplementationAddress(_exchangeId), _implementation, "Incorrect implementation was set"); 34 | 35 | //Address which is not owner of contract should not be able to call the upgradeTo function 36 | vm.expectRevert("Ownable: caller is not the owner"); 37 | vm.prank(address(0)); 38 | _proxyFactory.upgradeTo(_exchangeId, _implementation); 39 | } 40 | 41 | function tesGmxtWeth() public{ 42 | assertEq(_proxyFactory.weth(), _weth, "WETH was not correctly returned"); 43 | } 44 | 45 | function testGmxSetMaintainer() public{ 46 | assertEq(_proxyFactory.getMainatinerStatus(_maintainer), true); 47 | 48 | //Address which is not owner of contract should not be able to call the setMaintainer function 49 | vm.expectRevert("Ownable: caller is not the owner"); 50 | vm.prank(address(0)); 51 | _proxyFactory.setMaintainer(_maintainer, true); 52 | } 53 | 54 | function testGmxSetExchangeConfig() public{ 55 | _proxyFactory.setExchangeConfig(_exchangeId, gmxExchangeConfigs); 56 | assertEq(_proxyFactory.getExchangeConfig(_exchangeId), gmxExchangeConfigs); 57 | 58 | //Maintainer should be able to call the function 59 | vm.prank(_maintainer); 60 | _proxyFactory.setExchangeConfig(_exchangeId + 1, gmxExchangeConfigs); 61 | assertEq(_proxyFactory.getExchangeConfig(_exchangeId + 1), gmxExchangeConfigs); 62 | 63 | //Address which is not owner or maintiner of contract should not be able to call the setExchangeConfig function 64 | vm.expectRevert("OnlyMaintainerOrAbove"); 65 | vm.prank(address(0)); 66 | _proxyFactory.setExchangeConfig(_exchangeId, gmxExchangeConfigs); 67 | } 68 | 69 | function testGmxGetConfigVersions() public{ 70 | uint32 exchangeConfigVersion = _proxyFactory.getConfigVersions(_exchangeId); 71 | assertEq(exchangeConfigVersion, 0); 72 | 73 | exchangeConfigVersion = _proxyFactory.getConfigVersions(_exchangeId + 1); 74 | assertEq(exchangeConfigVersion, 0); 75 | 76 | //Updating asset and exchange config once again to check if the version is increasing 77 | _proxyFactory.setExchangeConfig(_exchangeId, gmxExchangeConfigs); 78 | 79 | (exchangeConfigVersion) = _proxyFactory.getConfigVersions(_exchangeId); 80 | assertEq(exchangeConfigVersion, 1); 81 | } 82 | 83 | //The following test cases are not possible to write until we have an implementation contract live on mainnet / testnet 84 | //ToDo - write test for openPosition 85 | //ToDo - write test for closePosition 86 | //ToDo - write test for closeOrder 87 | //ToDo - test the getProxyExchangeId function 88 | //ToDo - test the getExchangeProxy function 89 | //ToDo - test the getTradingProxy function 90 | } 91 | -------------------------------------------------------------------------------- /test/test_gmxSetUp.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | pragma solidity ^0.8.17; 3 | 4 | contract Setup{ 5 | address _wbtc = 0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f; //Arb1 Wbtc 6 | address _weth = 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1; //Arb1 Weth 7 | address _dai = 0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1; //Arb1 DAI 8 | 9 | address _implementation = 0x81CE58B23FC61a78C38574F760d8D77530f1EF9D; // GMX Adapter implementation on Arb1 10 | address _maintainer = 0x07068065bEdb261CfBC172648DBfDE38bC81dfD0; 11 | address _account = 0x07068065bEdb261CfBC172648DBfDE38bC81dfD0; 12 | 13 | uint256 _exchangeId = 1; 14 | 15 | uint256[] public gmxExchangeConfigs = new uint256[](7); 16 | 17 | function setUpGmxConfig() public{ 18 | //GMX Exchange config details 19 | gmxExchangeConfigs[0] = uint256(bytes32(bytes20(0x489ee077994B6658eAfA855C308275EAd8097C4A))); //GMX Vault address 20 | gmxExchangeConfigs[1] = uint256(bytes32(bytes20(0xb87a436B93fFE9D75c5cFA7bAcFff96430b09868))); //GMX Position Router address 21 | gmxExchangeConfigs[2] = uint256(bytes32(bytes20(0x09f77E8A13De9a35a7231028187e9fD5DB8a2ACB))); //GMX Order Book address 22 | gmxExchangeConfigs[3] = uint256(bytes32(bytes20(0xaBBc5F99639c9B6bCb58544ddf04EFA6802F4064))); //GMX Router address 23 | gmxExchangeConfigs[4] = uint256(bytes32(bytes20(0x0000000000000000000000000000000000000000))); //GMX Referral Code 24 | gmxExchangeConfigs[5] = 120; //GMX market order time limit 25 | gmxExchangeConfigs[6] = 172800; //GMX limit order time limit 26 | gmxExchangeConfigs.push(500); // initial margin rate 27 | gmxExchangeConfigs.push(0); // maintainence margin rate 28 | } 29 | } -------------------------------------------------------------------------------- /test/test_muxAdapter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | pragma solidity ^0.8.17; 3 | 4 | import "../lib/forge-std/src/Test.sol"; 5 | 6 | import "../lib/openzeppelin-contracts/contracts/proxy/beacon/BeaconProxy.sol"; 7 | import "../lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; 8 | 9 | import "../src/interfaces/IMuxAggregator.sol"; 10 | import "../src/interfaces/IWETH.sol"; 11 | 12 | import "../src/aggregators/mux/MuxAdapter.sol"; 13 | import "./test_muxSetUp.sol"; 14 | import "../src/aggregators/mux/Types.sol"; 15 | 16 | contract Token is ERC20 { 17 | constructor() ERC20("TestToken", "TT") { 18 | _mint(msg.sender, 1000 ether); 19 | } 20 | } 21 | 22 | contract TestMuxAdapter is Test, Setup{ 23 | IMuxProxyFactory private _proxyFactory; 24 | Token public token; 25 | IERC20 private _erc20; 26 | IWETH private _iweth; 27 | 28 | IMuxAggregator private _muxAdapterProxyLong; 29 | IMuxAggregator private _muxAdapterProxyShort; 30 | MuxAdapter private _muxAdapterInstance; 31 | 32 | event OpenPosition(uint8 collateralId, uint8 indexId, bool isLong, PositionContext context); 33 | event ClosePosition(uint8 collateralId, uint8 indexId, bool isLong, PositionContext context); 34 | event Withdraw( 35 | address collateralAddress, 36 | address account, 37 | uint256 balance 38 | ); 39 | 40 | function setUp() public { 41 | _muxAdapterInstance = new MuxAdapter(_weth); 42 | setUpMuxConfig(); 43 | token = new Token(); 44 | 45 | //For the sake of this testing, the TestGmxAdapter contract will be acting like proxyFactory. Therefore, we mock all the calls made by GmxAdapter to Proxy factory with address(this) 46 | //Mock implementation() call for creating Aggregator contract 47 | vm.mockCall(address(this), abi.encodeWithSelector(_proxyFactory.implementation.selector), abi.encode(_muxAdapterInstance)); 48 | //Mock call to factory during _updateConfigs() 49 | vm.mockCall(address(this), abi.encodeWithSelector(_proxyFactory.getConfigVersions.selector), abi.encode(1, 1)); 50 | vm.mockCall(address(this), abi.encodeWithSelector(_proxyFactory.getExchangeConfig.selector), abi.encode(muxExchangeConfigs)); 51 | //Mock transferFrom calls to collateral tokens 52 | vm.mockCall(address(_wbtc), abi.encodeWithSelector(_erc20.transferFrom.selector), abi.encode()); 53 | vm.mockCall(address(_dai), abi.encodeWithSelector(_erc20.transferFrom.selector), abi.encode()); 54 | 55 | // ----------- Long Position Initialization ---------------- 56 | address proxyLong; 57 | //for long position on GMX, the collateral and asset token are the same. 58 | bytes32 proxyIdLong = keccak256(abi.encodePacked(_exchangeId, _account, _wbtc, uint8(4), uint8(4), true)); 59 | bytes memory initDataLong = abi.encodeWithSignature( 60 | "initialize(uint256,address,address,uint8,uint8,bool)", 61 | _exchangeId, 62 | _account, 63 | _wbtc, 64 | uint8(4), 65 | uint8(4), 66 | true 67 | ); 68 | bytes memory bytecodeLong = abi.encodePacked(type(BeaconProxy).creationCode, abi.encode(address(this), initDataLong)); 69 | assembly { 70 | proxyLong := create2(0x0, add(0x20, bytecodeLong), mload(bytecodeLong), proxyIdLong) 71 | } 72 | require(proxyLong != address(0), "CreateFailed"); 73 | 74 | _muxAdapterProxyLong = IMuxAggregator(proxyLong); 75 | 76 | // ----------- Short Position Initialization ---------------- 77 | address proxyShort; 78 | //for short position on GMX, the collateral token is always a stable coin. 79 | bytes32 proxyIdShort = keccak256(abi.encodePacked(_exchangeId, _account, _dai, uint8(2), uint8(3), false)); 80 | bytes memory initDataShort = abi.encodeWithSignature( 81 | "initialize(uint256,address,address,uint8,uint8,bool)", 82 | _exchangeId, 83 | _account, 84 | _dai, 85 | uint8(2), 86 | uint8(3), 87 | false 88 | ); 89 | bytes memory bytecodeShort = abi.encodePacked(type(BeaconProxy).creationCode, abi.encode(address(this), initDataShort)); 90 | assembly { 91 | proxyShort := create2(0x0, add(0x20, bytecodeShort), mload(bytecodeShort), proxyIdShort) 92 | } 93 | require(proxyShort != address(0), "CreateFailed"); 94 | 95 | _muxAdapterProxyShort = IMuxAggregator(proxyShort); 96 | 97 | // Transfer some tokens to the withdraw contract 98 | token.transfer(address(_muxAdapterProxyLong), 500 ether); 99 | } 100 | 101 | function testMuxAdapterInitialization() public{ 102 | AccountState memory currentAccountLong = _muxAdapterProxyLong.accountState(); 103 | assertEq(currentAccountLong.account, _account); 104 | assertEq(currentAccountLong.collateralToken, _wbtc); 105 | assertEq(currentAccountLong.collateralId, uint8(4)); 106 | assertEq(currentAccountLong.indexId, uint8(4)); 107 | assertEq(currentAccountLong.isLong, true); 108 | 109 | AccountState memory currentAccountShort = _muxAdapterProxyShort.accountState(); 110 | assertEq(currentAccountShort.account, _account); 111 | assertEq(currentAccountShort.collateralToken, _dai); 112 | assertEq(currentAccountShort.collateralId, uint8(2)); 113 | assertEq(currentAccountShort.indexId, uint8(3)); 114 | assertEq(currentAccountShort.isLong, false); 115 | } 116 | 117 | function testMuxAdapterOpenPosition() public{ 118 | PositionOrderExtra memory extra = PositionOrderExtra({ 119 | tpslProfitTokenId : 0, 120 | tpPrice : 0, 121 | slPrice : 0, 122 | tpslDeadline : 0 123 | }); 124 | uint8 flags = 0x80 + 0x40; //Open Position Order | Market Order 125 | PositionContext memory openOrderContext; 126 | vm.expectEmit(true, true, true, false); 127 | emit OpenPosition(uint8(4), uint8(4), true, openOrderContext); 128 | //Placing a long open position order with collateral of 0.18 BTC and size $600. price is 0 since it is a Market Order, price of both asset and collateral (BTC) is given as $26451.3 and profitToken is USDC 129 | _muxAdapterProxyLong.placePositionOrder(18000000, 600000000000000000000, 0, flags, 26451300000000000000000, 26451300000000000000000, 0, true, uint8(0), 0x0000000000000000000000000000000000000000, extra); 130 | 131 | flags = 0x80; //Open Position Order | limit Order 132 | vm.expectEmit(true, true, true, false); 133 | emit OpenPosition(uint8(2), uint8(3), false, openOrderContext); 134 | //Placing a short open position order with collateral of 18 DAI and size $600. price is $20451.3 for the Limit Order, price of asset (BTC) is given as $26451.3, collateral price (DAI) at $1 and profitToken is USDC 135 | _muxAdapterProxyShort.placePositionOrder(180000000000000000000, 600000000000000000000, 20451300000000000000000, flags, 26451300000000000000000, 1000000000000000000, uint32(block.timestamp+10), false, uint8(0), 0x0000000000000000000000000000000000000000, extra); 136 | 137 | flags = 0x80 + 0x08; //Open Position Order | TPSL Order 138 | //Take Profit price for BTC at $28451.2 and Stop Loss price for BTC at 24451.2 139 | extra = PositionOrderExtra({ 140 | tpslProfitTokenId : 4, 141 | tpPrice : 28451300000000000000000, 142 | slPrice : 24451300000000000000000, 143 | tpslDeadline : uint32(block.timestamp+100) 144 | }); 145 | vm.expectEmit(true, true, true, false); 146 | emit OpenPosition(uint8(4), uint8(4), true, openOrderContext); 147 | //Placing a long open position position with collateral of 0.18 BTC and size $600. price is 0 for the TPSL Order, price of both asset and collateral (BTC) is given as $26451.3 and profitToken is USDC 148 | _muxAdapterProxyLong.placePositionOrder(18000000, 600000000000000000000, 0, flags, 26451300000000000000000, 26451300000000000000000, uint32(block.timestamp+100), true, uint8(0), 0x0000000000000000000000000000000000000000, extra); 149 | } 150 | 151 | function testMuxAdapterClosePosition() public{ 152 | PositionOrderExtra memory extra = PositionOrderExtra({ 153 | tpslProfitTokenId : 0, 154 | tpPrice : 0, 155 | slPrice : 0, 156 | tpslDeadline : 0 157 | }); 158 | uint8 flags = 0x40; //Open Position Order | Market Order 159 | PositionContext memory closeOrderContext; 160 | vm.expectEmit(true, true, true, false); 161 | emit ClosePosition(uint8(4), uint8(4), true, closeOrderContext); 162 | //Placing a long close position order with collateral of 0.18 BTC and size $600. price is 0 since it is a Market Order, price of both asset and collateral (BTC) is given as $26451.3 and profitToken is USDC 163 | _muxAdapterProxyLong.placePositionOrder(18000000, 600000000000000000000, 0, flags, 26451300000000000000000, 1000000000000000000, 0, true, uint8(4), 0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f, extra); 164 | 165 | flags = 0x0; //Open Position Order | limit Order 166 | vm.expectEmit(true, true, true, false); 167 | emit ClosePosition(uint8(2), uint8(3), false, closeOrderContext); 168 | //Placing a short close position order with collateral of 18 DAI and size $600. price is $20451.3 for the Limit Order, price of asset (BTC) is given as $26451.3, collateral price (DAI) at $1 and profitToken is USDC 169 | _muxAdapterProxyShort.placePositionOrder(180000000000000000000, 600000000000000000000, 28451300000000000000000, flags, 26451300000000000000000, 26451300000000000000000, uint32(block.timestamp+10), false, uint8(3), 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1, extra); 170 | 171 | //Test Cancel Orders 172 | uint64[] memory ordersBefore = _muxAdapterProxyLong.getPendingOrderKeys(); 173 | uint256 startOrdersLength = ordersBefore.length; 174 | 175 | assertEq(startOrdersLength > 0, true, "0 starting Orders"); 176 | _muxAdapterProxyLong.cancelOrders(ordersBefore); 177 | uint64[] memory ordersAfter = _muxAdapterProxyLong.getPendingOrderKeys(); 178 | uint256 endOrdersLength = ordersAfter.length; 179 | assertEq(endOrdersLength < startOrdersLength, true, "All Orders not cancelled"); 180 | assertEq(endOrdersLength == 0, true, "All Orders not cancelled"); 181 | } 182 | 183 | function testMuxAdapterGetSubAccountId() public{ 184 | //0xC34eA1F3114977Cb9Ed364dA50D6fBE5feB32Ee9 185 | bytes32 requiredSubAccountId = bytes32( 186 | (uint256(uint160(address(_muxAdapterProxyLong))) << 96) | 187 | (uint256(uint8(4)) << 88) | 188 | (uint256(uint8(4)) << 80) | 189 | (uint256(1) << 72) 190 | ); 191 | bytes32 muxSubAccountId = _muxAdapterProxyLong.getSubAccountId(); 192 | assertEq(muxSubAccountId, requiredSubAccountId); 193 | } 194 | 195 | function testWithdrawTokens() public{ 196 | uint256 initialBalance = token.balanceOf(address(_account)); 197 | uint256 contractBalance = token.balanceOf(address(_muxAdapterProxyLong)); 198 | 199 | _muxAdapterProxyLong.withdraw_tokens(address(token)); 200 | 201 | uint256 finalBalance = token.balanceOf(address(_account)); 202 | uint256 finalContractBalance = token.balanceOf(address(_muxAdapterProxyLong)); 203 | 204 | assertEq(finalBalance - initialBalance, contractBalance); 205 | assertEq(finalContractBalance, 0); 206 | } 207 | } -------------------------------------------------------------------------------- /test/test_muxProxyFactory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | pragma solidity ^0.8.17; 3 | 4 | import "../lib/forge-std/src/Test.sol"; 5 | import "../src/muxProxyFactory/muxProxyFactory.sol"; 6 | import "../lib/openzeppelin-contracts/contracts/proxy/beacon/IBeacon.sol"; 7 | import "./test_muxSetUp.sol"; 8 | 9 | contract TestProxyFactory is Test, Setup{ 10 | 11 | //Note test for createProxy done in muxAdapter tests while initializing the contracts. 12 | 13 | MuxProxyFactory private _proxyFactory; 14 | 15 | function setUp() public { 16 | _proxyFactory = new MuxProxyFactory(); 17 | 18 | setUpMuxConfig(); 19 | 20 | _proxyFactory.initialize(_weth); 21 | _proxyFactory.upgradeTo(_exchangeId, _implementation); 22 | 23 | //Set Maintainer 24 | _proxyFactory.setMaintainer(_maintainer, true); 25 | } 26 | 27 | function testMuxInitialize() public{ 28 | assertEq(_proxyFactory.weth(), _weth, "WETH was not correctly initialized"); 29 | assertEq(_proxyFactory.owner(), address(this), "Owner was not correctly initialized"); 30 | } 31 | 32 | function testMuxUpgradeTo() public{ 33 | assertEq(_proxyFactory.getImplementationAddress(_exchangeId), _implementation, "Incorrect implementation was set"); 34 | 35 | //Address which is not owner of contract should not be able to call the upgradeTo function 36 | vm.expectRevert("Ownable: caller is not the owner"); 37 | vm.prank(address(0)); 38 | _proxyFactory.upgradeTo(_exchangeId, _implementation); 39 | } 40 | 41 | function tesMuxWeth() public{ 42 | assertEq(_proxyFactory.weth(), _weth, "WETH was not correctly returned"); 43 | } 44 | 45 | function testMuxSetMaintainer() public{ 46 | assertEq(_proxyFactory.getMainatinerStatus(_maintainer), true); 47 | 48 | //Address which is not owner of contract should not be able to call the setMaintainer function 49 | vm.expectRevert("Ownable: caller is not the owner"); 50 | vm.prank(address(0)); 51 | _proxyFactory.setMaintainer(_maintainer, true); 52 | } 53 | 54 | function testMuxSetExchangeConfig() public{ 55 | _proxyFactory.setExchangeConfig(_exchangeId, muxExchangeConfigs); 56 | assertEq(_proxyFactory.getExchangeConfig(_exchangeId), muxExchangeConfigs); 57 | 58 | //Maintainer should be able to call the function 59 | vm.prank(_maintainer); 60 | _proxyFactory.setExchangeConfig(_exchangeId + 1, muxExchangeConfigs); 61 | assertEq(_proxyFactory.getExchangeConfig(_exchangeId + 1), muxExchangeConfigs); 62 | 63 | //Address which is not owner or maintiner of contract should not be able to call the setExchangeConfig function 64 | vm.expectRevert("OnlyMaintainerOrAbove"); 65 | vm.prank(address(0)); 66 | _proxyFactory.setExchangeConfig(_exchangeId, muxExchangeConfigs); 67 | } 68 | 69 | function testMuxGetConfigVersions() public{ 70 | (uint32 exchangeConfigVersion) = _proxyFactory.getConfigVersions(_exchangeId); 71 | assertEq(exchangeConfigVersion, 0); 72 | 73 | (exchangeConfigVersion) = _proxyFactory.getConfigVersions(_exchangeId + 1); 74 | assertEq(exchangeConfigVersion, 0); 75 | 76 | //Updating asset and exchange config once again to check if the version is increasing 77 | _proxyFactory.setExchangeConfig(_exchangeId, muxExchangeConfigs); 78 | 79 | (exchangeConfigVersion) = _proxyFactory.getConfigVersions(_exchangeId); 80 | assertEq(exchangeConfigVersion, 1); 81 | } 82 | 83 | function testSetAggregationFee() public{ 84 | _proxyFactory.setAggregationFee(2, true, address(this)); 85 | uint256 fee = _proxyFactory.getAggregationFee(); 86 | bool openPositionAggregationFeeStatus = _proxyFactory.getOpenAggregationFeeStatus(); 87 | address payable feeCollector = _proxyFactory.getFeeCollectorAddress(); 88 | 89 | assertEq(fee, 2); 90 | assertEq(openPositionAggregationFeeStatus, true); 91 | assertEq(feeCollector, payable(address(this))); 92 | } 93 | 94 | //The following test cases are not possible to write until we have an implementation contract live on mainnet / testnet 95 | //ToDo - write test for openPosition 96 | //ToDo - write test for closePosition 97 | //ToDo - write test for cancelOrder 98 | //ToDo - test the getExchangeProxy function 99 | //ToDo - test the getTradingProxy function 100 | } 101 | -------------------------------------------------------------------------------- /test/test_muxSetUp.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | pragma solidity ^0.8.17; 3 | 4 | contract Setup{ 5 | address _wbtc = 0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f; //Arb1 Wbtc 6 | address _weth = 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1; //Arb1 Weth 7 | address _dai = 0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1; //Arb1 DAI 8 | address _usdc = 0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8; //Arb1 USDC 9 | 10 | address _maintainer = 0x07068065bEdb261CfBC172648DBfDE38bC81dfD0; 11 | address _account = 0x07068065bEdb261CfBC172648DBfDE38bC81dfD0; 12 | 13 | address _implementation = 0x81CE58B23FC61a78C38574F760d8D77530f1EF9D; 14 | 15 | uint256 _exchangeId = 2; 16 | 17 | uint256[] public muxExchangeConfigs = new uint256[](3); 18 | 19 | function setUpMuxConfig() public{ 20 | //GMX Exchange config details 21 | muxExchangeConfigs[0] = uint256(bytes32(bytes20(0x3e0199792Ce69DC29A0a36146bFa68bd7C8D6633))); //MUX Liquidity Pool 22 | muxExchangeConfigs[1] = uint256(bytes32(bytes20(0xa19fD5aB6C8DCffa2A295F78a5Bb4aC543AAF5e3))); //MUX Order Book 23 | muxExchangeConfigs[2] = uint256(bytes32(bytes20(0x0000000000000000000000000000000000000000))); //GMX Referral Code 24 | } 25 | } --------------------------------------------------------------------------------