PendingExposureRouter.sol
1 pragma solidity >=0.6.0; 2 pragma experimental ABIEncoderV2; 3 4 import "../deployment/Deployer.sol"; 5 import "../deployment/ManagedContract.sol"; 6 import "../interfaces/IProtocolSettings.sol"; 7 import "../interfaces/UnderlyingFeed.sol"; 8 import "../interfaces/IOptionsExchange.sol"; 9 import "../interfaces/IGovernableLiquidityPool.sol"; 10 11 //import "../interfaces/external/canto/ITurnstile.sol"; 12 13 import "../utils/Convert.sol"; 14 import "../utils/MoreMath.sol"; 15 import "../utils/SafeCast.sol"; 16 import "../utils/SafeERC20.sol"; 17 import "../utils/SafeMath.sol"; 18 import "../utils/SignedSafeMath.sol"; 19 20 21 contract PendingExposureRouter is ManagedContract { 22 //contract PendingExposureRouter { 23 24 using SafeCast for uint; 25 using SafeERC20 for IERC20_2; 26 using SafeMath for uint; 27 using SignedSafeMath for int; 28 29 IProtocolSettings private settings; 30 IOptionsExchange private exchange; 31 32 uint256 slippageDenom = 10000; 33 34 struct PendingOrder { 35 bool canceled; 36 bool filled; 37 address account; 38 bool[] isApproved; 39 uint256[] buyPrice; 40 uint256[] sellPrice; 41 uint256[] maxBuyPrice; 42 uint256 slippage; 43 uint256 cancelAfter; 44 IOptionsExchange.OpenExposureInputs oEi; 45 } 46 47 mapping(uint => PendingOrder) public pendingMarketOrders; 48 uint256 pendingMarketOrdersCount; 49 50 event PendingMarketOrder( 51 address indexed trader, 52 uint orderId 53 ); 54 event AcceptedMarketOrder( 55 address indexed trader, 56 uint orderId 57 ); 58 event CanceledMarketOrder( 59 address indexed trader, 60 uint orderId 61 ); 62 63 function initialize(Deployer deployer) override internal { 64 settings = IProtocolSettings(deployer.getContractAddress("ProtocolSettings")); 65 exchange = IOptionsExchange(deployer.getContractAddress("OptionsExchange")); 66 67 /* 68 ITurnstile(0xfA428cA13C63101b537891daE5658785C82b0750).assign( 69 ITurnstile(0xfA428cA13C63101b537891daE5658785C82b0750).register(address(settings)) 70 ); 71 */ 72 } 73 74 function getMaxPendingMarketOrders() public view returns (uint256) { 75 if (pendingMarketOrdersCount == 0) { 76 return 0; 77 } else { 78 return pendingMarketOrdersCount.sub(1); 79 } 80 } 81 82 function cancelOrder(uint256 orderId) public { 83 require( 84 (msg.sender == pendingMarketOrders[orderId].account) || (isPrivledgedPublisherKeeper(orderId, msg.sender) != address(0)) || (pendingMarketOrders[orderId].cancelAfter > block.timestamp), 85 "unauthorized cancel" 86 ); 87 88 for(uint i=0; i<pendingMarketOrders[orderId].oEi.symbols.length; i++){ 89 if (pendingMarketOrders[orderId].oEi.isCovered[i]) { 90 //refund proper underlying here 91 address optAddr = exchange.resolveToken(pendingMarketOrders[orderId].oEi.symbols[i]); 92 IOptionsExchange.OptionData memory optData = exchange.getOptionData(optAddr); 93 address underlying = UnderlyingFeed( 94 optData.udlFeed 95 ).getUnderlyingAddr(); 96 IERC20_2(underlying).transfer( 97 pendingMarketOrders[orderId].account, 98 Convert.from18DecimalsBase(underlying, pendingMarketOrders[orderId].oEi.volume[i]) 99 ); 100 } 101 102 if (pendingMarketOrders[orderId].oEi.paymentTokens[i] != address(0)) { 103 //refund collateral to buy options 104 uint256 amountToTransfer = pendingMarketOrders[orderId].maxBuyPrice[i].mul(pendingMarketOrders[orderId].oEi.volume[i]).div(exchange.volumeBase()); 105 IERC20_2(pendingMarketOrders[orderId].oEi.paymentTokens[i]).transfer( 106 pendingMarketOrders[orderId].account, 107 Convert.from18DecimalsBase(pendingMarketOrders[orderId].oEi.paymentTokens[i], amountToTransfer) 108 ); 109 } 110 } 111 // clear order 112 pendingMarketOrders[orderId].canceled = true; 113 114 emit CanceledMarketOrder(pendingMarketOrders[orderId].account, orderId); 115 116 } 117 118 function approveOrder(uint256 orderId, string[] memory symbols) public { 119 address pendingPPk = isPrivledgedPublisherKeeper(orderId, msg.sender); 120 require(pendingPPk != address(0), "unauthorized approval"); 121 require(pendingMarketOrders[orderId].canceled == false, "already canceled"); 122 require(pendingMarketOrders[orderId].filled == false, "already filled"); 123 124 if(pendingMarketOrders[orderId].cancelAfter < block.timestamp) { 125 cancelOrder(orderId); 126 } else { 127 uint256 currentApprovals = 0; 128 //check approvals by that msg.sender matches privledgedPublisherKeeper for selected symbols in order 129 130 IOptionsExchange.OpenExposureInputs memory oEi = pendingMarketOrders[orderId].oEi; 131 bool[] memory ca = new bool[](oEi.symbols.length); 132 for (uint i=0; i< oEi.symbols.length; i++) { 133 string memory currSym = oEi.symbols[i]; 134 bool isApprovable = foundSymbol(currSym, symbols); 135 136 if (isApprovable == false) { 137 if (pendingMarketOrders[orderId].isApproved[i]) { 138 currentApprovals = currentApprovals.add(1); 139 } 140 continue; 141 } 142 143 address optAddr = exchange.resolveToken( 144 currSym 145 ); 146 147 IOptionsExchange.OptionData memory optData = exchange.getOptionData(optAddr); 148 address ppk = UnderlyingFeed(optData.udlFeed).getPrivledgedPublisherKeeper(); 149 if (ppk == msg.sender) { 150 ca[i] = true; 151 } 152 153 if ((pendingMarketOrders[orderId].isApproved[i] == false) && (ca[i] == true) && isApprovable == true) { 154 pendingMarketOrders[orderId].isApproved[i] = true; 155 currentApprovals = currentApprovals.add(1); 156 } else if (pendingMarketOrders[orderId].isApproved[i]) { 157 currentApprovals = currentApprovals.add(1); 158 } 159 } 160 161 // execute orders if enough approvals 162 if (currentApprovals == oEi.symbols.length){ 163 // handle approvals 164 bool isCanceled = false; 165 for(uint i=0; i<oEi.symbols.length; i++){ 166 if (pendingMarketOrders[orderId].oEi.isCovered[i]) { 167 //try to approve proper underlying here 168 169 address optAddr = exchange.resolveToken(oEi.symbols[i]); 170 IOptionsExchange.OptionData memory optData = exchange.getOptionData(optAddr); 171 address underlying = UnderlyingFeed( 172 optData.udlFeed 173 ).getUnderlyingAddr(); 174 175 IERC20_2(underlying).approve( 176 address(exchange), 177 Convert.from18DecimalsBase(underlying, oEi.volume[i]) 178 ); 179 } 180 181 if (oEi.paymentTokens[i] != address(0)) { 182 183 (uint256 _buyPrice,) = IGovernableLiquidityPool(oEi.poolAddrs[i]).queryBuy(oEi.symbols[i], true); 184 185 if (pendingMarketOrders[orderId].maxBuyPrice[i] < _buyPrice) { 186 //check buy slippage here: if (queue price * (1 +slippage)) < current price -> cancel order 187 isCanceled = true; 188 } else { 189 //renormalize volume 190 //oEi.volume[i] = oEi.volume[i].mul(pendingMarketOrders[orderId].maxBuyPrice[i]).div(_buyPrice); 191 192 //collateral to approve buy options 193 uint256 amountToTransfer = pendingMarketOrders[orderId].maxBuyPrice[i].mul(oEi.volume[i]).div(exchange.volumeBase()); 194 uint256 cAmountToTransfer = Convert.from18DecimalsBase(oEi.paymentTokens[i], amountToTransfer); 195 IERC20_2(oEi.paymentTokens[i]).approve( 196 address(exchange), 197 cAmountToTransfer 198 ); 199 IERC20_2(oEi.paymentTokens[i]).transfer(address(exchange), cAmountToTransfer); 200 } 201 } else { 202 203 uint256 slippageAmount = pendingMarketOrders[orderId].sellPrice[i].mul(pendingMarketOrders[orderId].slippage).div(slippageDenom); 204 205 (uint256 _sellPrice,) = IGovernableLiquidityPool(oEi.poolAddrs[i]).queryBuy(oEi.symbols[i], false); 206 207 if (pendingMarketOrders[orderId].sellPrice[i].sub(slippageAmount) > _sellPrice) { 208 //sell, check sell price slippage here: if (queue price * (1 - slippage)) > current price -> cancel order 209 isCanceled = true; 210 } 211 } 212 } 213 214 if (isCanceled == false) { 215 //execute order 216 exchange.openExposure( 217 oEi, 218 pendingMarketOrders[orderId].account 219 ); 220 221 pendingMarketOrders[orderId].filled = true; 222 emit AcceptedMarketOrder(pendingMarketOrders[orderId].account, orderId); 223 } else { 224 cancelOrder(orderId); 225 emit CanceledMarketOrder(pendingMarketOrders[orderId].account, orderId); 226 } 227 } 228 } 229 } 230 231 function addOrder(IOptionsExchange.OpenExposureInputs memory toEi) private { 232 pendingMarketOrdersCount++; 233 uint256 orderId = getMaxPendingMarketOrders(); 234 235 IOptionsExchange.OpenExposureInputs memory oEi; 236 237 oEi.symbols = new string[](toEi.symbols.length); 238 oEi.volume = new uint[](toEi.symbols.length); 239 oEi.isShort = new bool[](toEi.symbols.length); 240 oEi.isCovered = new bool[](toEi.symbols.length); 241 oEi.poolAddrs = new address[](toEi.symbols.length); 242 oEi.paymentTokens = new address[](toEi.symbols.length); 243 244 bool[] memory isApproved = new bool[](toEi.symbols.length); 245 uint256[] memory buyPrice = new uint256[](toEi.symbols.length); 246 uint256[] memory sellPrice = new uint256[](toEi.symbols.length); 247 uint256[] memory maxBuyPrice = new uint256[](toEi.symbols.length); 248 249 250 pendingMarketOrders[orderId] = PendingOrder( 251 false, 252 false, 253 address(0), 254 isApproved, 255 buyPrice, 256 sellPrice, 257 maxBuyPrice, 258 0, 259 0, 260 oEi 261 ); 262 } 263 264 function createOrder( 265 IOptionsExchange.OpenExposureInputs memory oEi, 266 uint256 cancelAfter, 267 uint256 slippage 268 ) public { 269 270 271 require( 272 (oEi.symbols.length == oEi.volume.length) && (oEi.symbols.length == oEi.isShort.length) && (oEi.symbols.length == oEi.isCovered.length) && (oEi.symbols.length == oEi.poolAddrs.length) && (oEi.symbols.length == oEi.paymentTokens.length), 273 "order params dim mismatch" 274 ); 275 276 addOrder(oEi); 277 uint256 orderId = getMaxPendingMarketOrders(); 278 279 for(uint i=0; i<oEi.symbols.length; i++){ 280 pendingMarketOrders[orderId].isApproved[i] = false; 281 pendingMarketOrders[orderId].buyPrice[i] = 0; 282 if (oEi.isCovered[i]) { 283 //try to transfer proper underlying here 284 285 address optAddr = exchange.resolveToken(oEi.symbols[i]); 286 IOptionsExchange.OptionData memory optData = exchange.getOptionData(optAddr); 287 address underlying = UnderlyingFeed( 288 optData.udlFeed 289 ).getUnderlyingAddr(); 290 IERC20_2(underlying).safeTransferFrom( 291 msg.sender, 292 address(this), 293 Convert.from18DecimalsBase(underlying, oEi.volume[i]) 294 ); 295 } 296 297 if (oEi.paymentTokens[i] != address(0)) { 298 //collateral to buy options 299 (uint256 _price,) = IGovernableLiquidityPool(oEi.poolAddrs[i]).queryBuy(oEi.symbols[i], true); 300 uint256 slippageAmount = _price.mul(slippage).div(slippageDenom); 301 uint256 amountToTransfer = (_price.add(slippageAmount)).mul(oEi.volume[i]).div(exchange.volumeBase()); 302 IERC20_2(oEi.paymentTokens[i]).safeTransferFrom( 303 msg.sender, 304 address(this), 305 Convert.from18DecimalsBase(oEi.paymentTokens[i], amountToTransfer) 306 ); 307 pendingMarketOrders[orderId].buyPrice[i] = _price; 308 pendingMarketOrders[orderId].maxBuyPrice[i] = (_price.add(slippageAmount)); 309 } else { 310 //TODO: sell, store sell price 311 (uint256 _price,) = IGovernableLiquidityPool(oEi.poolAddrs[i]).queryBuy(oEi.symbols[i], false); 312 pendingMarketOrders[orderId].sellPrice[i] = _price; 313 } 314 } 315 316 pendingMarketOrders[orderId].account = msg.sender; 317 pendingMarketOrders[orderId].oEi = oEi; 318 pendingMarketOrders[orderId].cancelAfter = cancelAfter; 319 pendingMarketOrders[orderId].slippage = slippage; 320 321 emit PendingMarketOrder(pendingMarketOrders[orderId].account, orderId); 322 323 } 324 325 function isPrivledgedPublisherKeeper(uint256 orderId, address caller) private view returns (address) { 326 for (uint i=0; i< pendingMarketOrders[orderId].oEi.symbols.length; i++) { 327 address optAddr = exchange.resolveToken(pendingMarketOrders[orderId].oEi.symbols[i]); 328 IOptionsExchange.OptionData memory optData = exchange.getOptionData(optAddr); 329 address ppk = UnderlyingFeed(optData.udlFeed).getPrivledgedPublisherKeeper(); 330 if (ppk == caller) { 331 return ppk; 332 } 333 } 334 335 return address(0); 336 } 337 338 function foundSymbol(string memory symbol, string[] memory symbols) private pure returns (bool) { 339 for (uint i = 0; i < symbols.length; i++) { 340 string memory tmps = symbols[i]; 341 if (strcmp(tmps, symbol) == true) { 342 return true; 343 } 344 } 345 346 return false; 347 } 348 349 function memcmp(bytes memory a, bytes memory b) private pure returns(bool){ 350 return (a.length == b.length) && (keccak256(a) == keccak256(b)); 351 } 352 function strcmp(string memory a, string memory b) private pure returns(bool){ 353 return memcmp(bytes(a), bytes(b)); 354 } 355 }