/ contracts / finance / PendingExposureRouter.sol
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  }