/ contracts / finance / OptionsExchange.sol
OptionsExchange.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/IGovernableLiquidityPool.sol";
  9  import "../interfaces/ICreditProvider.sol";
 10  import "../interfaces/IOptionsExchange.sol";
 11  import "../interfaces/IBaseCollateralManager.sol";
 12  import "../interfaces/IUnderlyingVault.sol";
 13  import "../interfaces/IOptionToken.sol";
 14  import "../interfaces/ILinearLiquidityPoolFactory.sol";
 15  import "../interfaces/IDEXFeedFactory.sol";
 16  import "../interfaces/IOptionTokenFactory.sol";
 17  
 18  import "../utils/Arrays.sol";
 19  import "../utils/Convert.sol";
 20  import "../utils/ERC20.sol"; //issues with verifying and ERC20 that can reference other ERC20
 21  import "../utils/MoreMath.sol";
 22  import "../utils/SafeCast.sol";
 23  import "../utils/SafeERC20.sol";
 24  import "../utils/SafeMath.sol";
 25  import "../utils/SignedSafeMath.sol";
 26  
 27  contract OptionsExchange is ERC20, ManagedContract {
 28  
 29      using SafeCast for uint;
 30      using SafeERC20 for IERC20_2;
 31      using SafeMath for uint;
 32      using SignedSafeMath for int;
 33      
 34      IUnderlyingVault private vault;
 35      IProtocolSettings private settings;
 36      ICreditProvider private creditProvider;
 37      IDEXFeedFactory private dexFeedFactory;
 38      IBaseCollateralManager private collateralManager;
 39  
 40      IOptionTokenFactory private optionTokenFactory;
 41      ILinearLiquidityPoolFactory private poolFactory;
 42  
 43      mapping(address => uint) public collateral;
 44  
 45      mapping(address => IOptionsExchange.OptionData) private options;
 46      mapping(address => IOptionsExchange.FeedData) private feeds;//DEPRICATED
 47      mapping(address => address[]) private book;
 48  
 49      mapping(string => address) private poolAddress;
 50      mapping(string => address) private tokenAddress;
 51      mapping(address => address) private dexFeedAddress;
 52  
 53      uint private _volumeBase;
 54  
 55      string private constant _name = "DeFi Options DAO Dollar";
 56      string private constant _symbol = "DODv2-DODD";
 57  
 58      string[] public poolSymbols;
 59  
 60      address private pendingExposureRouterAddr;
 61  
 62      
 63      event WithdrawTokens(address indexed from, uint value);
 64      event CreatePool(address indexed token, address indexed sender, address indexed owner);
 65      event CreateSymbol(address indexed token, address indexed sender);
 66      event CreateDexFeed(address indexed feed, address indexed sender);
 67  
 68      event WriteOptions(
 69          address indexed token,
 70          address indexed issuer,
 71          address indexed onwer,
 72          uint volume
 73      );
 74  
 75      constructor() ERC20(_name) public {
 76          
 77      }
 78  
 79      function initialize(Deployer deployer) override internal {
 80          creditProvider = ICreditProvider(deployer.getContractAddress("CreditProvider"));
 81          settings = IProtocolSettings(deployer.getContractAddress("ProtocolSettings"));
 82          optionTokenFactory = IOptionTokenFactory(deployer.getContractAddress("OptionTokenFactory"));
 83          poolFactory  = ILinearLiquidityPoolFactory(deployer.getContractAddress("LinearLiquidityPoolFactory"));
 84          collateralManager = IBaseCollateralManager(deployer.getContractAddress("CollateralManager"));
 85          vault = IUnderlyingVault(deployer.getContractAddress("UnderlyingVault"));
 86          pendingExposureRouterAddr = deployer.getContractAddress("PendingExposureRouter");
 87  
 88          _volumeBase = 1e18;
 89      }
 90  
 91      function volumeBase() external view returns (uint) {
 92          return _volumeBase;
 93      }
 94  
 95      function name() override external view returns (string memory) {
 96          return _name;
 97      }
 98  
 99      function symbol() override external view returns (string memory) {
100          return _symbol;
101      }
102  
103      function totalSupply() override public view returns (uint) {
104          return creditProvider.getTotalBalance();
105      }
106  
107      function depositTokens(address to, address token, uint value) public {
108  
109          IERC20_2(token).safeTransferFrom(msg.sender, address(creditProvider), value);
110          creditProvider.addBalance(to, token, value);
111      }
112  
113      function balanceOf(address owner) override public view returns (uint) {
114  
115          return creditProvider.balanceOf(owner);
116      }
117  
118      function transfer(address to, uint value) override external returns (bool) {
119          creditProvider.transferBalance(msg.sender, to, value);
120          ensureFunds(msg.sender);
121          emitTransfer(msg.sender, to, value);
122          return true;
123      }
124  
125  
126      function transferFrom(address from, address to, uint value) override public returns (bool) {
127  
128          uint allw = allowed[from][msg.sender];
129          if (allw >= value) {
130              allowed[from][msg.sender] = allw.sub(value);
131          } else {
132              creditProvider.ensureCaller(msg.sender);
133          }
134          creditProvider.transferBalance(from, to, value);
135          ensureFunds(from);
136  
137          emitTransfer(from, to, value);
138          return true;
139      }
140  
141      function transferBalance(
142          address from, 
143          address to, 
144          uint value
145      )
146          external
147      {
148  
149          uint allw = allowed[from][msg.sender];
150          if (allw >= value) {
151              allowed[from][msg.sender] = allw.sub(value);
152          } else {
153              creditProvider.ensureCaller(msg.sender);
154          }
155          creditProvider.transferBalance(from, to, value);
156          ensureFunds(from);
157      }
158  
159      function transferBalance(address to, uint value) external {
160          creditProvider.transferBalance(msg.sender, to, value);
161          ensureFunds(msg.sender);
162      }
163  
164      function withdrawTokens(address[] calldata tokensInOrder, uint[] calldata amountsOutInOrder) external {
165  
166          uint value;
167          for (uint i = 0; i < tokensInOrder.length; i++) {
168              value = value.add(amountsOutInOrder[i]);
169          }
170          
171          require(value <= calcSurplus(msg.sender), "insufficient surplus");
172          creditProvider.withdrawTokens(msg.sender, value, tokensInOrder, amountsOutInOrder);
173          emit WithdrawTokens(msg.sender, value);
174      }
175  
176      function createSymbol(
177          address udlFeed,
178          IOptionsExchange.OptionType optType,
179          uint strike, 
180          uint maturity
181      )
182          public
183          returns (address tk)
184      {
185          require(settings.getUdlFeed(udlFeed) > 0, "feed not allowed");
186          (IOptionsExchange.OptionData memory opt, string memory symbol) = createOptionInMemory(udlFeed, optType, strike, maturity);
187  
188          require(tokenAddress[symbol] == address(0), "already created");
189          tk = optionTokenFactory.create(symbol, udlFeed);
190          tokenAddress[symbol] = tk;
191          options[tk] = opt;
192  
193          emit CreateSymbol(tk, msg.sender);
194      }
195  
196      function createPool(string calldata nameSuffix, string calldata symbolSuffix, bool _onlyMintToOwner, address _owner) external returns (address pool) {
197  
198          require(poolAddress[symbolSuffix] == address(0), "already created");
199          pool = poolFactory.create(nameSuffix, symbolSuffix, _onlyMintToOwner, _owner);
200          poolAddress[symbolSuffix] = pool;
201          creditProvider.insertPoolCaller(pool);
202  
203          poolSymbols.push(symbolSuffix);
204          emit CreatePool(pool, msg.sender, _owner);
205      }
206  
207      function totalPoolSymbols() external view returns (uint) {
208          return poolSymbols.length;
209      }
210  
211      function getPoolAddress(string calldata poolSymbol) external view returns (address)  {
212          return poolAddress[poolSymbol];
213      }
214  
215      function createDexFeed(address underlying, address stable, address dexTokenPair) external returns (address feedAddr) {
216          require(dexFeedAddress[dexTokenPair] == address(0), "already created");
217          address feedAddr = dexFeedFactory.create(underlying, stable, dexTokenPair);
218          dexFeedAddress[dexTokenPair] = feedAddr;
219  
220          emit CreateDexFeed(feedAddr, msg.sender);
221      }
222  
223      function getDexFeedAddress(address dexTokenPair) external view returns (address)  {
224          return dexFeedAddress[dexTokenPair];
225      }
226      
227      function getOptionSymbol(
228          address udlFeed,
229          IOptionsExchange.OptionType optType,
230          uint strike, 
231          uint maturity
232      )
233          public
234          view
235          returns (string memory symbol)
236      {    
237          symbol = string(abi.encodePacked(
238              UnderlyingFeed(udlFeed).symbol(),
239              "-",
240              "E",
241              optType == IOptionsExchange.OptionType.CALL ? "C" : "P",
242              "-",
243              MoreMath.toString(strike),
244              "-",
245              MoreMath.toString(maturity),
246              "-",
247              Address.toAsciiString(udlFeed)
248          ));
249      }
250  
251      function openExposure(
252          IOptionsExchange.OpenExposureInputs memory oEi,
253          address to
254      ) public {
255          /*require(
256              (oEi.symbols.length == oEi.volume.length)  && 
257              (oEi.symbols.length == oEi.isShort.length) && 
258              (oEi.symbols.length == oEi.isCovered.length) && 
259              (oEi.symbols.length == oEi.poolAddrs.length) && 
260              (oEi.symbols.length == oEi.paymentTokens.length), 
261              "array mismatch"
262          );*/
263  
264          IOptionsExchange.OpenExposureVars memory oEx;
265  
266          // BIG QUESTION not sure if run time gas requirements will allow for this
267              //- calc collateral gas question
268              //- gas cost of multiple buy/sell txs from pool, maybe need optimized function for pool
269          // validate that all the symbols exist, pool has prices, revert if not
270          // make all the options buys/sells
271          // compute collateral reqirements with uncovred volumes
272  
273          oEx._tokens = new address[](oEi.symbols.length);
274          oEx._uncovered = new uint[](oEi.symbols.length);
275          oEx._holding = new uint[](oEi.symbols.length);
276  
277          address recipient = msg.sender;
278  
279          //if msg.sender is pending order router, need to set proper recipient of positions
280          if(pendingExposureRouterAddr == msg.sender) {
281              recipient = to;
282          }
283  
284          for (uint i=0; i< oEi.symbols.length; i++) {
285              oEx = getOpenExposureInternalArgs(i, oEx, oEi);
286              require(tokenAddress[oEx.symbol] != address(0), "symbol not available");
287              oEx._tokens[i] = tokenAddress[oEx.symbol];
288              IGovernableLiquidityPool pool = IGovernableLiquidityPool(oEx.poolAddr);
289              uint _price;    
290              if (oEi.isShort[i] == true) {
291                  //sell options
292                  if (oEx.vol > 0) {
293                      openExposureInternal(oEx.symbol, oEx.isCovered, oEx.vol, to, recipient, oEx.isRehypothicate, oEx.rehypothicationManager);
294                      if (msg.sender == oEx.poolAddr){
295                          //if the pool is the one writing the option to a user, transfer from exchange to user
296                          IERC20_2(oEx._tokens[i]).transfer(to, oEx.vol);
297                      } else {
298                          //this will credit exchange addr that needs to be transfered the seller
299                          (_price,) = pool.queryBuy(oEx.symbol, false);
300                          IERC20_2(oEx._tokens[i]).approve(address(pool), oEx.vol);
301                          pool.sell(oEx.symbol, _price, oEx.vol);
302                          creditProvider.transferBalance(
303                              address(this), 
304                              to, 
305                              _price.mul(oEx.vol).div(_volumeBase)
306                          );
307                      }
308                      //if not covered option
309                      if (oEx.isCovered == false) {
310                          oEx._uncovered[i] = oEx.vol;
311                      }
312                  }
313              } else {
314                  // buy options
315                  if (oEx.vol > 0) {
316                      //need to approve pool to spend payment tokens
317                      (_price,) = pool.queryBuy(oEx.symbol, true);
318                      //TODO: CAN ONLY BUY WITH EXCHANGE BALANCE
319                      require (oEi.paymentTokens[i] == address(this), "only with ex bal");
320                      uint pvalue = _price.mul(oEx.vol).div(_volumeBase);
321                      
322                      /*
323                      if (oEi.paymentTokens[i] != address(this)) {
324                          (uint tv, uint tb) = settings.getTokenRate(oEi.paymentTokens[i]);
325                          pvalue = pvalue.mul(tv).div(tb);
326                      }
327                      */
328  
329                      IERC20_2(oEi.paymentTokens[i]).approve(address(pool), pvalue);
330  
331                      //buy options from pool
332                      pool.buy(oEx.symbol, _price, oEx.vol, oEi.paymentTokens[i]);
333                      //transfer option token from exchange to user
334                      IERC20_2(oEx._tokens[i]).transfer(to, oEx.vol);
335                      oEx._holding[i] = oEx.vol;
336                  }
337              }
338          }
339  
340          //NOTE: MAY NEED TO ONLY COMPUTE THE ONES WRITTEN/BOUGHT HERE FOR GAS CONSTRAINTS
341          collateral[recipient] = collateral[recipient].add(
342              collateralManager.calcNetCollateral(oEx._tokens, oEx._uncovered, oEx._holding, true)
343          );
344          ensureFunds(recipient);
345      }
346  
347      function getOpenExposureInternalArgs(uint index, IOptionsExchange.OpenExposureVars memory oEx, IOptionsExchange.OpenExposureInputs memory oEi) private pure returns (IOptionsExchange.OpenExposureVars memory) {
348          oEx.symbol= oEi.symbols[index];
349          oEx.vol = oEi.volume[index];
350          oEx.isCovered = oEi.isCovered[index];
351          oEx.poolAddr = oEi.poolAddrs[index];
352          oEx.isRehypothicate = oEi.isRehypothicated[index];
353          oEx.rehypothicationManager = oEi.rehypothicationManagers[index];
354  
355          return oEx;
356      }
357  
358      function openExposureInternal(
359          string memory symbol,
360          bool isCovered,
361          uint volume,
362          address to,
363          address recipient,
364          bool isRehypothicate,
365          address rehypothicationManager
366      ) private {
367          address _tk = tokenAddress[symbol];
368          IOptionToken tk = IOptionToken(_tk);
369  
370          if (tk.writtenVolume(recipient) == 0 && tk.balanceOf(recipient) == 0) {
371              book[recipient].push(_tk);
372          }
373  
374  
375          if (msg.sender != to && tk.writtenVolume(to) == 0 && tk.balanceOf(to) == 0) {
376              book[to].push(_tk);
377          }
378  
379          //mint to exchange, then send pool (or send to user)
380          tk.issue(recipient, address(this), volume);
381          if (isCovered == true) {
382              //write covered
383              address underlying = UnderlyingFeed(
384                  options[_tk].udlFeed
385              ).getUnderlyingAddr();
386              IERC20_2(underlying).safeTransferFrom(
387                  msg.sender,
388                  address(vault), 
389                  Convert.from18DecimalsBase(underlying, volume)
390              );
391              vault.lock(recipient, _tk, volume, isRehypothicate, rehypothicationManager);
392          }
393          emit WriteOptions(_tk, recipient, to, volume);
394      }
395      
396      function transferOwnership(
397          string calldata symbol,
398          address from,
399          address to,
400          uint value
401      )
402          external
403      {
404          require(tokenAddress[symbol] == msg.sender, "unauthorized ownership transfer");        
405          IOptionToken tk = IOptionToken(msg.sender);
406          
407          if (tk.writtenVolume(from) == 0 && tk.balanceOf(from) == 0) {
408              Arrays.removeItem(book[from], msg.sender);
409          }
410  
411          if (tk.writtenVolume(to) == 0 && tk.balanceOf(to) == value) {
412              book[to].push(msg.sender);
413          }
414  
415          ensureFunds(from);
416      }
417  
418      function release(address owner, uint udl, uint coll) external {
419  
420          IOptionToken tk = IOptionToken(msg.sender);
421          require(tokenAddress[tk.symbol()] == msg.sender, "unauthorized release");
422  
423          IOptionsExchange.OptionData memory opt = options[msg.sender];
424  
425          if (udl > 0) {
426              vault.release(owner,  msg.sender, opt.udlFeed, udl);
427          }
428          
429          if (coll > 0) {
430              uint c = collateral[owner];
431              collateral[owner] = c.sub(
432                  MoreMath.min(c, IBaseCollateralManager(settings.getUdlCollateralManager(opt.udlFeed)).calcCollateral(opt, coll))
433              );
434          }
435      }
436  
437      function cleanUp(address owner, address _tk) public {
438  
439          IOptionToken tk = IOptionToken(_tk);
440  
441          if (tk.balanceOf(owner) == 0 && tk.writtenVolume(owner) == 0) {
442              Arrays.removeItem(book[owner], _tk);
443          }
444      }
445  
446      function calcSurplus(address owner) public view returns (uint) {
447          
448          uint coll = calcCollateral(owner, true); // multi udl feed refs
449          uint bal = balanceOf(owner);
450          if (bal >= coll) {
451              return bal.sub(coll);
452          }
453          return 0;
454      }
455  
456      function setCollateral(address owner) external {
457          collateral[owner] = calcCollateral(owner, true); // multi udl feed refs
458      }
459  
460      function calcCollateral(address owner, bool is_regular) public view returns (uint) {
461          return collateralManager.calcCollateral(owner, is_regular); // multi udl feed refs
462      }
463  
464      function calcCollateral(
465          address udlFeed,
466          uint volume,
467          IOptionsExchange.OptionType optType,
468          uint strike, 
469          uint maturity
470      )
471          public
472          view
473          returns (uint)
474      {
475          (IOptionsExchange.OptionData memory opt,) = createOptionInMemory(udlFeed, optType, strike, maturity);
476          return IBaseCollateralManager(settings.getUdlCollateralManager(opt.udlFeed)).calcCollateral(opt, volume);
477      }
478  
479      function calcExpectedPayout(address owner) external view returns (int payout) {
480          payout = collateralManager.calcExpectedPayout(owner); // multi udl feed refs
481      }
482      
483      function calcIntrinsicValue(address _tk) external view returns (int) {
484          IOptionsExchange.OptionData memory opt = options[_tk];
485          return IBaseCollateralManager(settings.getUdlCollateralManager(opt.udlFeed)).calcIntrinsicValue(options[_tk]);
486      }
487  
488      function calcIntrinsicValue(
489          address udlFeed,
490          IOptionsExchange.OptionType optType,
491          uint strike, 
492          uint maturity
493      )
494          public
495          view
496          returns (int)
497      {
498          (IOptionsExchange.OptionData memory opt,) = createOptionInMemory(udlFeed, optType, strike, maturity);
499          return IBaseCollateralManager(settings.getUdlCollateralManager(opt.udlFeed)).calcIntrinsicValue(opt);
500      }
501  
502      function resolveToken(string calldata symbol) external view returns (address addr) {
503          
504          addr = tokenAddress[symbol];
505          require(addr != address(0), "token not found");
506      }
507  
508      function burn(address owner, uint value, address _tk) external {
509          IOptionToken(_tk).burn(owner, value);
510      }
511  
512      function getOptionData(address tkAddr) external view returns (IOptionsExchange.OptionData memory) {
513          return options[tkAddr];
514      }
515  
516      function getBook(address owner)
517          external view
518          returns (
519              string memory symbols,
520              address[] memory tokens,
521              uint[] memory holding,
522              uint[] memory written,
523              uint[] memory uncovered,
524              int[] memory iv,
525              address[] memory underlying
526          )
527      {
528          tokens = book[owner];
529          holding = new uint[](tokens.length);
530          written = new uint[](tokens.length);
531          uncovered = new uint[](tokens.length);
532          iv = new int[](tokens.length);
533          underlying = new address[](tokens.length);
534  
535          for (uint i = 0; i < tokens.length; i++) {
536              IOptionToken tk = IOptionToken(tokens[i]);
537              IOptionsExchange.OptionData memory opt = options[tokens[i]];
538              if (i == 0) {
539                  symbols = getOptionSymbol(opt);
540              } else {
541                  symbols = string(abi.encodePacked(symbols, "\n", getOptionSymbol(opt)));
542              }
543              holding[i] = tk.balanceOf(owner);
544              written[i] = tk.writtenVolume(owner);
545              uncovered[i] = tk.uncoveredVolume(owner);
546              iv[i] = IBaseCollateralManager(settings.getUdlCollateralManager(opt.udlFeed)).calcIntrinsicValue(opt);
547              underlying[i] = UnderlyingFeed(opt.udlFeed).getUnderlyingAddr();
548  
549          }
550      }
551  
552      function ensureFunds(address owner) private view {
553          require(
554              balanceOf(owner) >= collateral[owner],
555              "insufficient collateral"
556          );
557      }
558  
559      function createOptionInMemory(
560          address udlFeed,
561          IOptionsExchange.OptionType optType,
562          uint strike, 
563          uint maturity
564      )
565          private
566          view
567          returns (IOptionsExchange.OptionData memory opt, string memory symbol)
568      {
569          opt = IOptionsExchange.OptionData(udlFeed, optType, strike.toUint120(), maturity.toUint32());
570          symbol = getOptionSymbol(opt);
571      }
572  
573      function getOptionSymbol(IOptionsExchange.OptionData memory opt) public view returns (string memory symbol) {    
574          symbol = getOptionSymbol(
575              opt.udlFeed,
576              opt._type,
577              opt.strike,
578              opt.maturity
579          );
580      }
581  }