/ contracts / finance / hedging / GNSHedgingManager.sol
GNSHedgingManager.sol
  1  /*
  2  
  3  - must use DAI
  4  
  5  - TOOD: NEED TO MAP UNDERLYING TOKENS ON NETWORK TO PAIR INDEX
  6  
  7  */
  8  
  9  pragma solidity >=0.6.0;
 10  pragma experimental ABIEncoderV2;
 11  
 12  
 13  import "./BaseHedgingManager.sol";
 14  import "../../interfaces/ICollateralManager.sol";
 15  import "../../interfaces/IGovernableLiquidityPool.sol";
 16  import "../../interfaces/external/gains_network/IGFarmTradingStorageV5.sol";
 17  import "../../interfaces/external/gains_network/IGNSTradingV6_2.sol";
 18  import "../../interfaces/external/gains_network/IGNSPairInfosV6_1.sol";
 19  import "../../interfaces/UnderlyingFeed.sol";
 20  import "../../interfaces/IGNSHedgingmanagerFactory.sol";
 21  import "../../utils/Convert.sol";
 22  
 23  
 24  contract GNSHedgingManager is BaseHedgingManager {
 25      address private dai;
 26      address private referrer;
 27      address private gnsTradingAddr;
 28      address private gnsPairInfoAddr;
 29      address private gnsFarmTradingStorageAddr;
 30      address private gnsHedgingManagerFactoryAddr;
 31  
 32      uint private maxLeverage = 150;
 33      uint private minLeverage = 4;
 34      uint private defaultLeverage = 15;
 35      uint MAX_UINT = uint(-1);
 36      uint PRECISION = 1e10;
 37  
 38      struct ExposureData {
 39          IERC20_2 t;
 40  
 41          int256 diff;
 42          int256 real;
 43          int256 ideal;
 44  
 45          uint256 r;
 46          uint256 b;
 47          
 48          uint256 pos_size;
 49          uint256 udlPrice;
 50          uint256 pairIndex;
 51          uint256 totalStables;
 52          uint256 poolLeverage;
 53          uint256 totalPosValue;
 54          uint256 totalPosValueToTransfer;
 55          
 56          address underlying;
 57  
 58          bool hasClosed;
 59  
 60          address[] at;
 61          address[] allowedTokens;
 62          uint256[] tv;
 63          
 64      }
 65  
 66      mapping(string => uint) pairIndexMap;
 67  
 68      //https://gains-network.gitbook.io/docs-home/what-is-gains-network/contract-addresses
 69      constructor(address _deployAddr, address _poolAddr) public {
 70          Deployer deployer = Deployer(_deployAddr);
 71          super.initialize(deployer);
 72          gnsHedgingManagerFactoryAddr = deployer.getContractAddress("GNSHedgingManagerFactory");
 73          gnsTradingAddr = IGNSHedgingManagerFactory(gnsHedgingManagerFactoryAddr)._gnsTradingAddr();
 74          gnsPairInfoAddr = IGNSHedgingManagerFactory(gnsHedgingManagerFactoryAddr)._gnsPairInfoAddr();
 75          gnsFarmTradingStorageAddr = IGNSHedgingManagerFactory(gnsHedgingManagerFactoryAddr)._gnsFarmTradingStorageAddr();
 76          referrer = IGNSHedgingManagerFactory(gnsHedgingManagerFactoryAddr)._referrer();
 77          dai = IGNSHedgingManagerFactory(gnsHedgingManagerFactoryAddr)._daiAddr();
 78          poolAddr = _poolAddr;
 79  
 80          //off by 1 on purpose
 81          //https://gains-network.gitbook.io/docs-home/gtrade-leveraged-trading/pair-list
 82          pairIndexMap["BTC/USD"] = 1;
 83          pairIndexMap["ETH/USD"] = 2;
 84          pairIndexMap["LINK/USD"] = 3;
 85          pairIndexMap["DOGE/USD"] = 4;
 86          pairIndexMap["MATIC/USD"] = 5;
 87          pairIndexMap["ADA/USD"] = 6;
 88          pairIndexMap["SUSHI/USD"] = 7;
 89          pairIndexMap["AAVE/USD"] = 8;
 90          pairIndexMap["MATIC/USD"] = 9;
 91          pairIndexMap["ADA/USD"] = 10;
 92          pairIndexMap["SUSHI/USD"] = 11;
 93          pairIndexMap["AAVE/USD"] = 12;
 94      }
 95  
 96      function pool() override external view returns (address) {
 97          return poolAddr;
 98      }
 99  
100      function getPosSize(address underlying, bool isLong) override public view returns (uint[] memory) {
101          uint[] memory data = new uint[](1);
102          return data;
103      }
104  
105      function getHedgeExposure(address underlying) override public view returns (int256) {
106          /*
107  
108          - get Position info
109              - I think if you go to this function 38. openTradesCount(address, pair_index) you will get back the index of your trade (from your 3 slots, so 0, 1 or 2)
110                  https://polygonscan.com/address/0xaee4d11a16B2bc65EDD6416Fb626EB404a6D65BD#readContract#F38
111              - Then the function just above it 37. openTrades(address, pair_index, index (the one you got back from the first))
112  
113              - https://polygonscan.com/address/0xaee4d11a16B2bc65EDD6416Fb626EB404a6D65BD#readContract#F37
114  
115          */
116          uint256 pairIndexOffByOne = pairIndexMap[UnderlyingFeed(underlying).symbol()];
117          require(pairIndexOffByOne > 0, "no pair available");
118          uint256 pairIndex = pairIndexOffByOne.sub(1);
119  
120  
121          uint256 tradeIdx = IGFarmTradingStorageV5(gnsFarmTradingStorageAddr).openTradesCount(address(this), pairIndex);
122          IGFarmTradingStorageV5.Trade memory tradeData = IGFarmTradingStorageV5(gnsFarmTradingStorageAddr).openTrades(address(this), pairIndex, tradeIdx);
123          IGFarmTradingStorageV5.TradeInfo memory tradeInfoData = IGFarmTradingStorageV5(gnsFarmTradingStorageAddr).openTradesInfo(address(this), pairIndex, tradeIdx);
124  
125          return (tradeData.buy == true) ? int256(tradeInfoData.openInterestDai) : int256(tradeInfoData.openInterestDai).mul(-1);
126      }
127      
128  
129      function idealHedgeExposure(address underlying) override public view returns (int256) {
130          // look at order book for poolAddr and compute the delta for the given underlying (depening on net positioning of the options outstanding and the side of the trade the poolAddr is on)
131          (,address[] memory _tokens, uint[] memory _holding,, uint[] memory _uncovered,, address[] memory _underlying) = exchange.getBook(poolAddr);
132  
133          int totalDelta = 0;
134          for (uint i = 0; i < _tokens.length; i++) {
135              address _tk = _tokens[i];
136              IOptionsExchange.OptionData memory opt = exchange.getOptionData(_tk);
137              if (_underlying[i] == underlying){
138                  int256 delta;
139  
140                  if (_uncovered[i].sub(_holding[i]) > 0) {
141                      // net short this option, thus mult by -1
142                      delta = ICollateralManager(
143                          settings.getUdlCollateralManager(opt.udlFeed)
144                      ).calcDelta(
145                          opt,
146                          _uncovered[i].sub(_holding[i])
147                      ).mul(-1);
148                  } else {
149                      // net long thus does not need to be modified
150                      delta = ICollateralManager(
151                          settings.getUdlCollateralManager(opt.udlFeed)
152                      ).calcDelta(
153                          opt,
154                          _holding[i]
155                      );
156                  }
157  
158                  totalDelta = totalDelta.add(delta);
159              }
160          }
161          return totalDelta;
162      }
163      
164      function realHedgeExposure(address udlFeedAddr) override public view returns (int256) {
165          // look at metavault exposure for underlying, and divide by asset price
166          (, int256 udlPrice) = UnderlyingFeed(udlFeedAddr).getLatestPrice();
167          int256 exposure = getHedgeExposure(udlFeedAddr);
168          return exposure.div(udlPrice);
169      }
170      
171      function balanceExposure(address udlFeedAddr) override external returns (bool) {
172          ExposureData memory exData;
173          exData.underlying = UnderlyingFeed(udlFeedAddr).getUnderlyingAddr();
174          exData.ideal = idealHedgeExposure(exData.underlying);
175          exData.real = realHedgeExposure(exData.underlying);
176          exData.diff = exData.ideal - exData.real;
177          exData.allowedTokens = settings.getAllowedTokens();
178          exData.totalStables = creditProvider.totalTokenStock();
179  
180          exData.pairIndex = pairIndexMap[UnderlyingFeed(udlFeedAddr).symbol()];
181          require(exData.pairIndex > 0, "cannot hedge underlying");
182          exData.pairIndex = exData.pairIndex.sub(1);
183  
184          exData.poolLeverage = (settings.isAllowedCustomPoolLeverage(poolAddr) == true) ? IGovernableLiquidityPool(poolAddr).getLeverage() : defaultLeverage;
185  
186  
187          require(exData.poolLeverage <= maxLeverage && exData.poolLeverage >= minLeverage, "leverage out of range");
188  
189          (, int256 udlPrice) = UnderlyingFeed(udlFeedAddr).getLatestPrice();
190          exData.udlPrice = uint256(udlPrice);
191  
192          //TODO: min trade size is 1500 dai
193  
194          if (exData.ideal >= 0) {
195              exData.pos_size = uint256(MoreMath.abs(exData.diff));
196              if (exData.real > 0) {
197                  //need to close long position first
198                  uint256 tradeIdx = IGFarmTradingStorageV5(gnsFarmTradingStorageAddr).openTradesCount(address(this), exData.pairIndex);
199                  //NOTE: THIS IS NOT ATOMIC, WILL NEED TO MANUALLY TRANSFER ANY RECIEVING DAI TO CREDIT PROVIDER AND MANUALLY CREDIT POOL BAL IN ANOTHER TX
200                  IGNSTradingV6_2(gnsTradingAddr).closeTradeMarket(
201                      exData.pairIndex,
202                      tradeIdx
203                  );
204                  exData.pos_size = uint256(exData.ideal);
205                  exData.hasClosed = true;
206  
207              }
208              
209              // increase short position by pos_size
210              if (exData.pos_size != 0) {
211                  exData.t = IERC20_2(dai);
212                  uint256 daiBal = exData.t.balanceOf(address(this));
213                  exData.totalPosValue = exData.pos_size.mul(exData.udlPrice);
214                  exData.totalPosValueToTransfer = exData.totalPosValue.div(exData.poolLeverage);
215  
216                  // hedging should fail if not enough stables in exchange
217                  ///TODO: IF NO DAI, SHOULD SWAP INTO DAI
218                  if (exData.totalStables.mul(exData.poolLeverage) > exData.totalPosValue) {
219  
220                      if (exData.totalPosValueToTransfer > 0) {                        
221                          uint v = MoreMath.min(
222                              exData.totalPosValueToTransfer,
223                              exData.t.balanceOf(address(creditProvider))
224                          );
225                          if (exData.t.allowance(address(this), gnsTradingAddr) > 0) {
226                              exData.t.safeApprove(gnsTradingAddr, 0);
227                          }
228                          exData.t.safeApprove(gnsTradingAddr, v);
229  
230                          //transfer collateral from credit provider to hedging manager and debit pool bal
231                          exData.at = new address[](1);
232                          exData.at[0] = dai;
233  
234                          exData.tv = new uint[](1);
235                          exData.tv[0] = v;
236  
237                          if (daiBal < exData.totalPosValueToTransfer) {
238                              ICollateralManager(
239                                  settings.getUdlCollateralManager(
240                                      udlFeedAddr
241                                  )
242                              ).borrowTokensByPreference(
243                                  address(this), poolAddr, v, exData.at, exData.tv
244                              );
245                          }
246  
247                          if (exData.hasClosed == false) {
248                              uint256 tradeIdx = IGFarmTradingStorageV5(gnsFarmTradingStorageAddr).openTradesCount(address(this), exData.pairIndex);
249                              IGNSTradingV6_2(gnsTradingAddr).closeTradeMarket(
250                                  exData.pairIndex,
251                                  tradeIdx
252                              );
253                          }
254  
255                          (uint priceImpactP, uint priceAfterImpact) = IGNSPairInfosV6_1(gnsPairInfoAddr).getTradePriceImpact(
256                              exData.udlPrice.mul(PRECISION).div(1e18),//uint openPrice,        // PRECISION
257                              exData.pairIndex,
258                              false,//bool long,
259                              exData.totalPosValue // 1e18 (DAI)
260                          ); /*external view returns(
261                              uint priceImpactP,     // PRECISION (%)
262                              uint priceAfterImpact  // PRECISION
263                          )*/
264  
265                          uint256 tradeIdx = IGFarmTradingStorageV5(gnsFarmTradingStorageAddr).openTradesCount(address(this), exData.pairIndex);
266  
267                          //SAMPLE TX OPEN: https://polygonscan.com/tx/0x5c593b45f2d5e459516666e54c942f23ff3c3991f2a33cde7570b43dd997ee43
268  
269                          StorageInterfaceV5.Trade memory t = StorageInterfaceV5.Trade(
270                              address(this),
271                              exData.pairIndex,
272                              tradeIdx,
273                              0,//uint initialPosToken,       // 1e18
274                              exData.totalPosValueToTransfer,//uint positionSizeDai,       // 1e18
275                              exData.udlPrice.mul(PRECISION).div(1e18),//uint openPrice,             // PRECISION
276                              false,//bool buy,
277                              exData.poolLeverage,//uint leverage,
278                              0,//uint tp,                    // PRECISION
279                              MAX_UINT//uint sl                    // PRECISION
280                          );
281  
282                          NftRewardsInterfaceV6.OpenLimitOrderType orderType = NftRewardsInterfaceV6.OpenLimitOrderType(0);
283  
284                          IGNSTradingV6_2(gnsTradingAddr).openTrade(
285                              t,//StorageInterfaceV5.Trade memory t,
286                              orderType,//NftRewardsInterfaceV6.OpenLimitOrderType orderType, // LEGACY => market
287                              0,//uint spreadReductionId
288                              priceImpactP, // for market orders only
289                              referrer
290                          );
291                      }
292                  }
293              }
294          } else if (exData.ideal < 0) {
295              exData.pos_size = uint256(MoreMath.abs(exData.diff));
296              if (exData.real < 0) {
297                  // need to close short position first
298                  uint256 tradeIdx = IGFarmTradingStorageV5(gnsFarmTradingStorageAddr).openTradesCount(address(this), exData.pairIndex);
299                  //NOTE: THIS IS NOT ATOMIC, WILL NEED TO MANUALLY TRANSFER ANY RECIEVING DAI TO CREDIT PROVIDER AND MANUALLY CREDIT POOL BAL IN ANOTHER TX
300                  IGNSTradingV6_2(gnsTradingAddr).closeTradeMarket(
301                      exData.pairIndex,
302                      tradeIdx
303                  );
304                  exData.pos_size = uint256(exData.ideal);
305                  exData.hasClosed = true;
306              }
307  
308              // increase long position by pos_size
309              if (exData.pos_size != 0) {
310                  exData.totalPosValue = exData.pos_size.mul(exData.udlPrice);
311                  exData.t = IERC20_2(dai);
312                  uint256 daiBal = exData.t.balanceOf(address(this));
313  
314                  exData.totalPosValueToTransfer = exData.totalPosValue.div(exData.poolLeverage);
315  
316                  // hedging should fail if not enough stables in exchange
317                  ///TODO: IF NO DAI, SHOULD SWAP INTO DAI
318                  if (exData.totalStables.mul(exData.poolLeverage) > exData.totalPosValue) {
319  
320                      if (exData.totalPosValueToTransfer > 0) {                        
321                          uint v = MoreMath.min(
322                              exData.totalPosValueToTransfer,
323                              exData.t.balanceOf(address(creditProvider))
324                          );
325                          if (exData.t.allowance(address(this), gnsTradingAddr) > 0) {
326                              exData.t.safeApprove(gnsTradingAddr, 0);
327                          }
328                          exData.t.safeApprove(gnsTradingAddr, v);
329  
330                          //transfer collateral from credit provider to hedging manager and debit pool bal
331                          exData.at = new address[](1);
332                          exData.at[0] = dai;
333  
334                          exData.tv = new uint[](1);
335                          exData.tv[0] = v;
336  
337                          if (daiBal < exData.totalPosValueToTransfer) {
338                              ICollateralManager(
339                                  settings.getUdlCollateralManager(
340                                      udlFeedAddr
341                                  )
342                              ).borrowTokensByPreference(
343                                  address(this), poolAddr, v, exData.at, exData.tv
344                              );
345                          }
346  
347                          if (exData.hasClosed == false) {
348                              uint256 tradeIdx = IGFarmTradingStorageV5(gnsFarmTradingStorageAddr).openTradesCount(address(this), exData.pairIndex);
349                              IGNSTradingV6_2(gnsTradingAddr).closeTradeMarket(
350                                  exData.pairIndex,
351                                  tradeIdx
352                              );
353                          }
354  
355                          (uint priceImpactP, uint priceAfterImpact) = IGNSPairInfosV6_1(gnsPairInfoAddr).getTradePriceImpact(
356                              exData.udlPrice.mul(PRECISION).div(1e18),//uint openPrice,        // PRECISION
357                              exData.pairIndex,
358                              true,//bool long,
359                              exData.totalPosValue // 1e18 (DAI)
360                          ); /*external view returns(
361                              uint priceImpactP,     // PRECISION (%)
362                              uint priceAfterImpact  // PRECISION
363                          )*/
364  
365                          uint256 tradeIdx = IGFarmTradingStorageV5(gnsFarmTradingStorageAddr).openTradesCount(address(this), exData.pairIndex);
366  
367                          //SAMPLE TX OPEN: https://polygonscan.com/tx/0x5c593b45f2d5e459516666e54c942f23ff3c3991f2a33cde7570b43dd997ee43
368  
369                          StorageInterfaceV5.Trade memory t = StorageInterfaceV5.Trade(
370                              address(this),
371                              exData.pairIndex,
372                              tradeIdx,
373                              0,//uint initialPosToken,       // 1e18
374                              exData.totalPosValueToTransfer,//uint positionSizeDai,       // 1e18
375                              exData.udlPrice.mul(PRECISION).div(1e18),//uint openPrice,             // PRECISION
376                              true,//bool buy,
377                              exData.poolLeverage,//uint leverage,
378                              MAX_UINT,//uint tp,                    // PRECISION
379                              0//uint sl                    // PRECISION
380                          );
381  
382                          NftRewardsInterfaceV6.OpenLimitOrderType orderType = NftRewardsInterfaceV6.OpenLimitOrderType(0);
383  
384                          IGNSTradingV6_2(gnsTradingAddr).openTrade(
385                              t,//StorageInterfaceV5.Trade memory t,
386                              orderType,//NftRewardsInterfaceV6.OpenLimitOrderType orderType, // LEGACY => market
387                              0,//uint spreadReductionId
388                              priceImpactP, // for market orders only
389                              referrer
390                          );
391                      }
392                  }
393              }
394          }
395      }
396  
397      function totalTokenStock() override public view returns (uint v) {
398  
399          address[] memory tokens = settings.getAllowedTokens();
400          for (uint i = 0; i < tokens.length; i++) {
401              (uint r, uint b) = settings.getTokenRate(tokens[i]);
402              uint value = IERC20_2(tokens[i]).balanceOf(address(this));
403              v = v.add(value.mul(b).div(r));
404          }
405      }
406  
407      function transferTokensToCreditProvider(address tokenAddr) override external {
408          //this needs to be used if/when liquidations happen and tokens sent from external contracts end up here
409          uint value = IERC20_2(tokenAddr).balanceOf(address(this));
410          IERC20_2(tokenAddr).safeTransfer(address(creditProvider), value);
411          creditProvider.creditPoolBalance(poolAddr, tokenAddr, value);    
412      }
413  }