/ contracts / pools / LiquidityPool.sol
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  }