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 }