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 }