LiquidityPool.sol
1 pragma experimental ABIEncoderV2; 2 pragma solidity >=0.6.0; 3 4 import "../deployment/ManagedContract.sol"; 5 import "../finance/RedeemableToken.sol"; 6 import "../finance/YieldTracker.sol"; 7 import "../governance/ProtocolSettings.sol"; 8 import "../interfaces/TimeProvider.sol"; 9 import "../interfaces/ILiquidityPool.sol"; 10 import "../interfaces/ICreditProvider.sol"; 11 import "../interfaces/IOptionToken.sol"; 12 import "../interfaces/IOptionsExchange.sol"; 13 import "../interfaces/UnderlyingFeed.sol"; 14 import "../utils/SafeERC20.sol"; 15 import "../utils/SafeCast.sol"; 16 import "../utils/MoreMath.sol"; 17 import "../utils/SignedSafeMath.sol"; 18 19 abstract contract LiquidityPool is ManagedContract, RedeemableToken, ILiquidityPool { 20 21 using SafeCast for uint; 22 using SafeERC20 for IERC20_2; 23 using SafeMath for uint; 24 using SignedSafeMath for int; 25 26 enum Operation { NONE, BUY, SELL } 27 28 struct PricingParameters { 29 address udlFeed; 30 IOptionsExchange.OptionType optType; 31 uint120 strike; 32 uint32 maturity; 33 uint32 t0; 34 uint32 t1; 35 uint120 buyStock; 36 uint120 sellStock; 37 uint120[] x; 38 uint120[] y; 39 } 40 41 struct Range { 42 uint120 start; 43 uint120 end; 44 } 45 46 TimeProvider private time; 47 ProtocolSettings private settings; 48 YieldTracker private tracker; 49 50 mapping(string => PricingParameters) private parameters; 51 mapping(string => mapping(uint => Range)) private ranges; 52 53 uint internal spread; 54 uint internal reserveRatio; 55 uint public withdrawFee; 56 uint public capacity; 57 uint private _maturity; 58 string[] private optSymbols; 59 60 uint private timeBase; 61 uint private sqrtTimeBase; 62 uint internal volumeBase; 63 uint internal fractionBase; 64 65 address private _hedgingManagerAddress; 66 67 constructor(string memory _name) ERC20(_name) public { 68 69 } 70 71 function initialize(Deployer deployer) virtual override internal { 72 73 DOMAIN_SEPARATOR = ERC20(getImplementation()).DOMAIN_SEPARATOR(); 74 75 time = TimeProvider(deployer.getContractAddress("TimeProvider")); 76 exchange = IOptionsExchange(deployer.getContractAddress("OptionsExchange")); 77 settings = ProtocolSettings(deployer.getContractAddress("ProtocolSettings")); 78 tracker = YieldTracker(deployer.getContractAddress("YieldTracker")); 79 80 timeBase = 1e18; 81 sqrtTimeBase = 1e9; 82 volumeBase = exchange.volumeBase(); 83 fractionBase = 1e9; 84 } 85 86 function setParameters( 87 uint _spread, 88 uint _reserveRatio, 89 uint _withdrawFee, 90 uint _capacity, 91 uint _mt, 92 address _hmngr 93 ) 94 external 95 { 96 ensureCaller(); 97 spread = _spread; 98 reserveRatio = _reserveRatio; 99 withdrawFee = _withdrawFee; 100 capacity = _capacity; 101 _maturity = _mt; 102 _hedgingManagerAddress = _hmngr; 103 } 104 105 function getHedgingManager() public view returns (address){ 106 return _hedgingManagerAddress; 107 } 108 109 function redeemAllowed() override public view returns (bool) { 110 111 return time.getNow() >= _maturity; 112 } 113 114 function maturity() override external view returns (uint) { 115 116 return _maturity; 117 } 118 119 function yield(uint dt) override external view returns (uint y) { 120 121 y = tracker.yield(address(this), dt); 122 } 123 124 function valueOf(address ownr) public view returns (uint) { 125 126 (uint bal, int pOut) = getBalanceAndPayout(); 127 return uint(int(bal).add(pOut)) 128 .mul(balanceOf(ownr)).div(totalSupply()); 129 } 130 131 function addSymbol( 132 address udlFeed, 133 uint strike, 134 uint _mt, 135 IOptionsExchange.OptionType optType, 136 uint t0, 137 uint t1, 138 uint120[] calldata x, 139 uint120[] calldata y, 140 uint buyStock, 141 uint sellStock 142 ) 143 external 144 { 145 ensureCaller(); 146 require(x.length > 0 && x.length.mul(2) == y.length && _mt < _maturity, "invalid pricing surface or maturity"); 147 148 IOptionsExchange.OptionData memory opt = IOptionsExchange.OptionData(udlFeed, optType, strike.toUint120(), _mt.toUint32()); 149 string memory optSymbol = exchange.getOptionSymbol(opt); 150 151 if (parameters[optSymbol].x.length == 0) { 152 optSymbols.push(optSymbol); 153 } 154 155 parameters[optSymbol] = PricingParameters( 156 udlFeed, 157 optType, 158 strike.toUint120(), 159 _mt.toUint32(), 160 t0.toUint32(), 161 t1.toUint32(), 162 buyStock.toUint120(), 163 sellStock.toUint120(), 164 x, 165 y 166 ); 167 168 emit AddSymbol(optSymbol); 169 } 170 171 function setRange(string calldata optSymbol, Operation op, uint start, uint end) external { 172 173 ensureCaller(); 174 ranges[optSymbol][uint(op)] = Range(start.toUint120(), end.toUint120()); 175 } 176 177 function removeSymbol(string calldata optSymbol) external { 178 179 ensureCaller(); 180 delete parameters[optSymbol]; 181 Arrays.removeItem(optSymbols, optSymbol); 182 emit RemoveSymbol(optSymbol); 183 } 184 185 function depositTokens( 186 address to, 187 address token, 188 uint value, 189 uint deadline, 190 uint8 v, 191 bytes32 r, 192 bytes32 s 193 ) 194 override 195 external 196 { 197 IERC20Permit(token).permit(msg.sender, address(this), value, deadline, v, r, s); 198 depositTokens(to, token, value); 199 } 200 201 function depositTokens(address to, address token, uint value) override public { 202 203 (uint b0, int po) = getBalanceAndPayout(); 204 depositTokensInExchange(token, value); 205 uint b1 = exchange.balanceOf(address(this)); 206 require(b1 <= capacity, "capacity exceeded"); 207 208 tracker.push(int(b0).add(po), b1.sub(b0).toInt256()); 209 210 uint ts = _totalSupply; 211 int expBal = po.add(int(b1)); 212 uint p = b1.sub(b0).mul(fractionBase).div(uint(expBal)); 213 214 uint b = 1e3; 215 uint v = ts > 0 ? 216 ts.mul(p).mul(b).div(fractionBase.sub(p)) : 217 uint(expBal).mul(b); 218 v = MoreMath.round(v, b); 219 220 addBalance(to, v); 221 _totalSupply = ts.add(v); 222 emitTransfer(address(0), to, v); 223 } 224 225 function withdraw(uint amount) override external { 226 227 uint bal = balanceOf(msg.sender); 228 require(bal >= amount, "insufficient caller balance"); 229 230 uint val = valueOf(msg.sender).mul(amount).div(bal); 231 uint discountedValue = val.mul(fractionBase.sub(withdrawFee)).div(fractionBase); 232 require(discountedValue <= calcFreeBalance(), "insufficient pool balance"); 233 234 (uint b0, int po) = getBalanceAndPayout(); 235 exchange.transferBalance(msg.sender, discountedValue); 236 tracker.push(int(b0).add(po), -(val.toInt256())); 237 238 removeBalance(msg.sender, amount); 239 _totalSupply = _totalSupply.sub(amount); 240 emitTransfer(msg.sender, address(0), amount); 241 } 242 243 function calcFreeBalance() public view returns (uint balance) { 244 245 uint exBal = exchange.balanceOf(address(this)); 246 uint reserve = exBal.mul(reserveRatio).div(fractionBase); 247 uint sp = exBal.sub(exchange.collateral(address(this))); 248 balance = sp > reserve ? sp.sub(reserve) : 0; 249 } 250 251 function listSymbols() override external view returns (string memory available) { 252 253 available = listSymbols(Operation.BUY); 254 } 255 256 function listSymbols(Operation op) public view returns (string memory available) { 257 258 for (uint i = 0; i < optSymbols.length; i++) { 259 if (isAvailable(optSymbols[i], op)) { 260 if (bytes(available).length == 0) { 261 available = optSymbols[i]; 262 } else { 263 available = string(abi.encodePacked(available, "\n", optSymbols[i])); 264 } 265 } 266 } 267 } 268 269 function isAvailable(string memory optSymbol, Operation op) public view returns (bool b) { 270 271 b = true; 272 273 if (parameters[optSymbol].maturity <= time.getNow()) { 274 275 b = false; 276 277 } else if (op == Operation.BUY || op == Operation.SELL) { 278 279 if (!isInRange(optSymbol, op, parameters[optSymbol].udlFeed)) { 280 b = false; 281 } else { 282 uint stock = op == Operation.BUY ? 283 uint(parameters[optSymbol].buyStock) : 284 uint(parameters[optSymbol].sellStock); 285 b = getAvailableStock(optSymbol, stock, op) > 0; 286 } 287 } 288 } 289 290 function queryBuy(string memory optSymbol) 291 override 292 public 293 view 294 returns (uint price, uint volume) 295 { 296 ensureValidSymbol(optSymbol); 297 PricingParameters memory param = parameters[optSymbol]; 298 price = calcOptPrice(param, Operation.BUY); 299 volume = MoreMath.min( 300 calcVolume(optSymbol, param, price, Operation.BUY), 301 getAvailableStock(optSymbol, uint(param.buyStock), Operation.BUY) 302 ); 303 } 304 305 function querySell(string memory optSymbol) 306 override 307 public 308 view 309 returns (uint price, uint volume) 310 { 311 ensureValidSymbol(optSymbol); 312 PricingParameters memory param = parameters[optSymbol]; 313 price = calcOptPrice(param, Operation.SELL); 314 volume = MoreMath.min( 315 calcVolume(optSymbol, param, price, Operation.SELL), 316 getAvailableStock(optSymbol, uint(param.sellStock), Operation.SELL) 317 ); 318 } 319 320 function buy( 321 string calldata optSymbol, 322 uint price, 323 uint volume, 324 address token, 325 uint maxValue, 326 uint deadline, 327 uint8 v, 328 bytes32 r, 329 bytes32 s 330 ) 331 override 332 external 333 returns (address _tk) 334 { 335 IERC20Permit(token).permit(msg.sender, address(this), maxValue, deadline, v, r, s); 336 _tk = buy(optSymbol, price, volume, token); 337 } 338 339 function buy(string memory optSymbol, uint price, uint volume, address token) 340 override 341 public 342 returns (address _tk) 343 { 344 require(volume > 0, "invalid volume"); 345 ensureValidSymbol(optSymbol); 346 347 PricingParameters memory param = parameters[optSymbol]; 348 349 require(isInRange(optSymbol, Operation.BUY, param.udlFeed), "out of range"); 350 351 price = receivePayment(param, price, volume, token); 352 353 _tk = exchange.resolveToken(optSymbol); 354 IOptionToken tk = IOptionToken(_tk); 355 uint _holding = tk.balanceOf(address(this)); 356 357 if (volume > _holding) { 358 writeOptions(tk, param, volume, msg.sender); 359 } else { 360 tk.transfer(msg.sender, volume); 361 } 362 363 emit Buy(_tk, msg.sender, price, volume); 364 } 365 366 function sell( 367 string memory optSymbol, 368 uint price, 369 uint volume, 370 uint deadline, 371 uint8 v, 372 bytes32 r, 373 bytes32 s 374 ) 375 override 376 public 377 { 378 require(volume > 0, "invalid volume"); 379 ensureValidSymbol(optSymbol); 380 381 PricingParameters memory param = parameters[optSymbol]; 382 383 require(isInRange(optSymbol, Operation.SELL, param.udlFeed), "out of range"); 384 385 price = validatePrice(price, param, Operation.SELL); 386 387 address _tk = exchange.resolveToken(optSymbol); 388 IOptionToken tk = IOptionToken(_tk); 389 if (deadline > 0) { 390 tk.permit(msg.sender, address(this), volume, deadline, v, r, s); 391 } 392 tk.transferFrom(msg.sender, address(this), volume); 393 394 uint _written = tk.writtenVolume(address(this)); 395 if (_written > 0) { 396 uint toBurn = MoreMath.min(_written, volume); 397 tk.burn(toBurn); 398 } 399 400 uint value = price.mul(volume).div(volumeBase); 401 exchange.transferBalance(msg.sender, value); 402 403 require(calcFreeBalance() > 0, "pool balance too low"); 404 405 uint _holding = tk.balanceOf(address(this)); 406 require(_holding <= param.sellStock, "excessive volume"); 407 408 emit Sell(_tk, msg.sender, price, volume); 409 } 410 411 function sell(string calldata optSymbol, uint price, uint volume) override external { 412 413 bytes32 x; 414 sell(optSymbol, price, volume, 0, 0, x, x); 415 } 416 417 function writeOptions( 418 IOptionToken tk, 419 PricingParameters memory param, 420 uint volume, 421 address to 422 ) 423 virtual 424 internal; 425 426 function calcOptPrice(PricingParameters memory p, Operation op) 427 virtual 428 internal 429 view 430 returns (uint price); 431 432 function calcVolume( 433 string memory optSymbol, 434 PricingParameters memory p, 435 uint price, 436 Operation op 437 ) 438 virtual 439 internal 440 view 441 returns (uint volume); 442 443 function getUdlPrice(address udlFeed) internal view returns (int udlPrice) { 444 445 UnderlyingFeed feed = UnderlyingFeed(udlFeed); 446 (, udlPrice) = feed.getLatestPrice(); 447 } 448 449 function getBalanceAndPayout() private view returns (uint bal, int pOut) { 450 451 bal = exchange.balanceOf(address(this)); 452 pOut = exchange.calcExpectedPayout(address(this)); 453 } 454 455 function isInRange( 456 string memory optSymbol, 457 Operation op, 458 address udlFeed 459 ) 460 private 461 view 462 returns(bool) 463 { 464 Range memory r = ranges[optSymbol][uint(op)]; 465 if (r.start == 0 && r.end == 0) { 466 return true; 467 } 468 int udlPrice = getUdlPrice(udlFeed); 469 return uint(udlPrice) >= r.start && uint(udlPrice) <= r.end; 470 } 471 472 function getAvailableStock( 473 string memory optSymbol, 474 uint stock, 475 Operation op 476 ) 477 private 478 view 479 returns (uint) 480 { 481 uint bal = 0; 482 IOptionToken tk = IOptionToken( 483 exchange.resolveToken(optSymbol) 484 ); 485 if (op == Operation.BUY) { 486 bal = tk.writtenVolume(address(this)); 487 } else { 488 bal = tk.balanceOf(address(this)); 489 } 490 return stock > bal ? stock - bal : 0; 491 } 492 493 function receivePayment( 494 PricingParameters memory param, 495 uint price, 496 uint volume, 497 address token 498 ) 499 private 500 returns (uint) 501 { 502 price = validatePrice(price, param, Operation.BUY); 503 uint value = price.mul(volume).div(volumeBase); 504 505 if (token != address(exchange)) { 506 (uint tv, uint tb) = settings.getTokenRate(token); 507 value = value.mul(tv).div(tb); 508 depositTokensInExchange(token, value); 509 } else { 510 exchange.transferBalance(msg.sender, address(this), value); 511 } 512 513 return price; 514 } 515 516 function validatePrice( 517 uint price, 518 PricingParameters memory param, 519 Operation op 520 ) 521 private 522 view 523 returns (uint p) 524 { 525 p = calcOptPrice(param, op); 526 require( 527 op == Operation.BUY ? price >= p : price <= p, 528 "insufficient price" 529 ); 530 } 531 532 function depositTokensInExchange(address token, uint value) private { 533 IERC20_2 t = IERC20_2(token); 534 t.safeTransferFrom(msg.sender, address(this), value); 535 t.safeApprove(address(exchange), value); 536 exchange.depositTokens(address(this), token, value); 537 } 538 539 function ensureValidSymbol(string memory optSymbol) private view { 540 541 require(parameters[optSymbol].udlFeed != address(0), "invalid optSymbol"); 542 } 543 544 function ensureCaller() private view { 545 546 require(msg.sender == getOwner(), "unauthorized caller"); 547 } 548 }