/ contracts / pools / GovernableLiquidityPoolV1.sol
GovernableLiquidityPoolV1.sol
  1  pragma solidity >=0.6.0;
  2  pragma experimental ABIEncoderV2;
  3  
  4  import "../deployment/ManagedContract.sol";
  5  import "../interfaces/IOptionToken.sol";
  6  import "../interfaces/IProposal.sol";
  7  import "../interfaces/IGovernableLiquidityPool.sol";
  8  import "../interfaces/IYieldTracker.sol";
  9  import "../interfaces/UnderlyingFeed.sol";
 10  import "../interfaces/IProtocolSettings.sol";
 11  import "../interfaces/IProposalWrapper.sol";
 12  import "../interfaces/IProposalManager.sol";
 13  import "../interfaces/IInterpolator.sol";
 14  import "../finance/RedeemableToken.sol";
 15  import "../utils/SafeERC20.sol";
 16  import "../utils/SafeCast.sol";
 17  import "../utils/MoreMath.sol";
 18  import "../utils/SignedSafeMath.sol";
 19  
 20  abstract contract GovernableLiquidityPoolV1 is ManagedContract, RedeemableToken, IGovernableLiquidityPool {
 21  
 22      using SafeERC20 for IERC20_2;
 23      using SafeCast for uint;
 24      using SafeMath for uint;
 25      using SignedSafeMath for int;
 26  
 27      string internal _name;
 28      string internal _symbol;
 29      string internal constant _symbol_prefix = "DODv1-LLPRTK-";
 30      string internal constant _name_prefix = "Linear Liquidity Pool Redeemable Token: ";
 31  
 32      IYieldTracker private tracker;
 33      IProtocolSettings private settings;
 34      IInterpolator internal interpolator;
 35      IProposalManager private proposalManager;
 36  
 37      mapping(string => PricingParameters) private parameters;
 38      mapping(string => mapping(uint => Range)) private ranges;
 39  
 40      uint public override maturity;
 41      uint internal volumeBase;
 42      uint public override withdrawFee;
 43      uint internal reserveRatio;
 44      uint internal fractionBase;
 45      
 46      string[] private optSymbols;
 47  
 48      constructor(string memory _nm, string memory _sb, address _deployAddr)
 49          ERC20(string(abi.encodePacked(_name_prefix, _nm)))
 50          public
 51      {    
 52          _symbol = _sb;
 53          _name = _nm;
 54  
 55          Deployer deployer = Deployer(_deployAddr);
 56          fractionBase = 1e9;
 57          exchange = IOptionsExchange(deployer.getContractAddress("OptionsExchange"));
 58          settings = IProtocolSettings(deployer.getContractAddress("ProtocolSettings"));
 59          tracker = IYieldTracker(deployer.getContractAddress("YieldTracker"));
 60          interpolator = IInterpolator(deployer.getContractAddress("Interpolator"));
 61          proposalManager = IProposalManager(deployer.getContractAddress("ProposalManager"));
 62          volumeBase = 1e18;//exchange.volumeBase();
 63      }
 64  
 65      function setParameters(
 66          uint _reserveRatio,
 67          uint _withdrawFee,
 68          uint _mt,
 69          uint _lm,
 70          address _hmngr,
 71          uint _ht
 72      )
 73          override external
 74      {
 75          ensureCaller();
 76          reserveRatio = _reserveRatio;
 77          withdrawFee = _withdrawFee;
 78          maturity = _mt;
 79      }
 80  
 81      function redeemAllowed() override public view returns (bool) {
 82          
 83          return block.timestamp >= maturity;
 84      }
 85  
 86      function yield(uint dt) override external view returns (uint) {
 87          return tracker.yield(address(this), dt);
 88      }
 89      
 90      function addSymbol(
 91          address udlFeed,
 92          uint strike,
 93          uint _mt,
 94          IOptionsExchange.OptionType optType,
 95          uint t0,
 96          uint t1,
 97          uint120[] calldata x,
 98          uint120[] calldata y,
 99          uint[3] calldata bsStockSpread
100      )
101          override external
102      {
103          ensureCaller();
104          require(x.length > 0 && x.length.mul(2) == y.length && _mt < maturity, "invalid pricing surface or maturity");
105  
106          string memory optSymbol = exchange.getOptionSymbol(
107              IOptionsExchange.OptionData(udlFeed, optType, strike.toUint120(), _mt.toUint32())
108          );
109  
110          if (parameters[optSymbol].x.length == 0) {
111              optSymbols.push(optSymbol);
112          }
113  
114          parameters[optSymbol] = PricingParameters(
115              udlFeed,
116              optType,
117              strike.toUint120(),
118              _mt.toUint32(),
119              t0.toUint32(),
120              t1.toUint32(),
121              bsStockSpread,
122              x,
123              y
124          );
125  
126          emit AddSymbol(optSymbol);
127      }
128  
129      function showSymbol(string calldata optSymbol) external view returns (address, uint32, uint, uint, uint, uint120[] memory, uint120[] memory) {
130          return (parameters[optSymbol].udlFeed, parameters[optSymbol].t1, parameters[optSymbol].bsStockSpread[0], parameters[optSymbol].bsStockSpread[1], parameters[optSymbol].bsStockSpread[2], parameters[optSymbol].x, parameters[optSymbol].y);
131      }
132  
133      function setRange(string calldata optSymbol, Operation op, uint start, uint end) external {
134          ensureCaller();
135          ranges[optSymbol][uint(op)] = Range(start.toUint120(), end.toUint120());
136      }
137  
138      function removeSymbol(string calldata optSymbol) external {
139          require(parameters[optSymbol].maturity >= block.timestamp, "cannot destroy befor maturity");        
140          Arrays.removeItem(optSymbols, optSymbol);
141      }
142  
143      function depositTokens(address to, address token, uint value) override public {
144          (uint b0, int po) = getBalanceAndPayout();
145          depositTokensInExchange(token, value);
146          uint b1 = exchange.balanceOf(address(this));
147          
148          tracker.push(int(b0).add(po), b1.sub(b0).toInt256());
149  
150          int expBal = po.add(int(b1));
151          uint p = b1.sub(b0).mul(fractionBase).div(uint(expBal));
152  
153          uint b = 1e3;
154          uint v = _totalSupply > 0 ?
155              _totalSupply.mul(p).mul(b).div(fractionBase.sub(p)) : 
156              uint(expBal).mul(b);
157          v = MoreMath.round(v, b);
158  
159          addBalance(to, v);
160          _totalSupply = _totalSupply.add(v);
161          emitTransfer(address(0), to, v);
162      }
163  
164      function withdraw(uint amount) override external {
165  
166          uint bal = balanceOf(msg.sender);
167          require(bal >= amount, "insufficient caller balance");
168  
169          uint val = valueOf(msg.sender).mul(amount).div(bal);
170          uint discountedValue = val.mul(fractionBase.sub(withdrawFee)).div(fractionBase);
171          uint freeBal = calcFreeBalance();
172          require(discountedValue <= freeBal, "insufficient pool balance");
173  
174          (uint b0, int po) = getBalanceAndPayout();
175          exchange.transferBalance(
176              msg.sender, 
177              discountedValue
178          );
179          tracker.push(
180              int(b0).add(po), 
181              -(val.toInt256())
182          );     
183  
184          removeBalance(msg.sender, amount);
185          _totalSupply = _totalSupply.sub(amount);
186          emitTransfer(msg.sender, address(0), amount);
187      }
188  
189      function calcFreeBalance() override public view returns (uint balance) {
190          uint exBal = exchange.balanceOf(address(this));
191          uint reserve = exBal.mul(reserveRatio).div(fractionBase);
192          uint sp = exBal.sub(exchange.collateral(address(this)));
193          balance = sp > reserve ? sp.sub(reserve) : 0;
194      }
195  
196      function listSymbols() override external view returns (string memory available) {
197          for (uint i = 0; i < optSymbols.length; i++) {
198              if (bytes(available).length == 0) {
199                  available = optSymbols[i];
200              } else {
201                  available = string(abi.encodePacked(available, "\n", optSymbols[i]));
202              }
203          }
204      }
205  
206      function queryBuy(string memory optSymbol, bool isBuy)
207          override
208          public
209          view
210          returns (uint price, uint volume)
211      {
212          (price, volume) = queryHelper(optSymbol, (isBuy == true) ? Operation.BUY : Operation.SELL);
213      }
214  
215      function queryHelper(string memory optSymbol, Operation op) private view returns (uint price, uint volume) {
216          PricingParameters memory param = parameters[optSymbol];
217          address _tk = exchange.resolveToken(optSymbol);
218          uint optBal = (op == Operation.SELL) ? IOptionToken(_tk).balanceOf(address(this)) : IOptionToken(_tk).writtenVolume(address(this));
219          uint optBalInv = (op == Operation.BUY) ? IOptionToken(_tk).balanceOf(address(this)) : IOptionToken(_tk).writtenVolume(address(this));
220          price = calcOptPrice(param, op, IOptionToken(_tk).balanceOf(address(this)), IOptionToken(_tk).writtenVolume(address(this)));
221  
222          volume = MoreMath.min(
223              calcVolume(optSymbol, param, price, op, optBalInv),
224              (op == Operation.SELL) ? uint(param.bsStockSpread[1].toUint120()).sub(optBal) : uint(param.bsStockSpread[0].toUint120()).sub(optBal)
225          );
226      }
227  
228      function buy(string memory optSymbol, uint price, uint volume, address token)
229          override
230          public
231          returns (address _tk)
232      {
233          PricingParameters memory param = parameters[optSymbol];
234          require(volume > 0 && isInRange(optSymbol, Operation.BUY, param.udlFeed), "out of range or invalid volume");
235  
236  
237          _tk = exchange.resolveToken(optSymbol);
238          (uint price, uint value) = receivePayment(param, price, volume, token, _tk);
239  
240          if (volume > IOptionToken(_tk).balanceOf(address(this))) {
241              writeOptions(_tk, param, volume, msg.sender);
242          } else {
243              IOptionToken(_tk).transfer(msg.sender, volume);
244          }
245  
246          emit Buy(_tk, msg.sender, price, volume);
247      }
248  
249      function sell(
250          string memory optSymbol,
251          uint price,
252          uint volume
253      )
254          override
255          public
256      {
257          PricingParameters memory param = parameters[optSymbol];        
258          require(volume > 0 && isInRange(optSymbol, Operation.SELL, param.udlFeed), "out of range or invalid volume");
259          address _tk = exchange.resolveToken(optSymbol);
260          price = validatePrice(price, param, Operation.SELL, _tk);
261  
262          IOptionToken tk = IOptionToken(_tk);
263          tk.transferFrom(msg.sender, address(this), volume);
264          
265          uint _written = tk.writtenVolume(address(this));
266          if (_written > 0) {
267              tk.burn(
268                  MoreMath.min(_written, volume)
269              );
270          }
271  
272          uint value = price.mul(volume).div(volumeBase);        
273          exchange.transferBalance(msg.sender, value);
274          // holding <= sellStock
275          require(calcFreeBalance() > 0 && tk.balanceOf(address(this)) <= param.bsStockSpread[1].toUint120(), "pool balance too low or excessive volume");
276          emit Sell(_tk, msg.sender, price, volume);
277      }
278  
279      function getBalanceAndPayout() private view returns (uint bal, int pOut) {
280          
281          bal = exchange.balanceOf(address(this));
282          pOut = exchange.calcExpectedPayout(address(this));
283      }
284  
285      function isInRange(
286          string memory optSymbol,
287          Operation op,
288          address udlFeed
289      )
290          private
291          view
292          returns(bool)
293      {
294          Range memory r = ranges[optSymbol][uint(op)];
295          if (r.start == 0 && r.end == 0) {
296              return true;
297          }
298          int udlPrice = getUdlPrice(udlFeed);
299          return uint(udlPrice) >= r.start && uint(udlPrice) <= r.end;
300      }
301  
302      function receivePayment(
303          PricingParameters memory param,
304          uint price,
305          uint volume,
306          address token,
307          address option
308      )
309          private
310          returns (uint, uint)
311      {
312          price = validatePrice(price, param, Operation.BUY, option);
313          uint value = price.mul(volume).div(volumeBase);
314  
315          if (token != address(exchange)) {
316              (uint tv, uint tb) = settings.getTokenRate(token);
317              value = value.mul(tv).div(tb);
318              depositTokensInExchange(token, value);
319          } else {
320              exchange.transferBalance(msg.sender, address(this), value);
321          }
322  
323          return (price, value);
324      }
325  
326      function validatePrice(
327          uint price, 
328          PricingParameters memory param, 
329          Operation op,
330          address _tk
331      ) 
332          private
333          view
334          returns (uint p) 
335      {
336          p = calcOptPrice(
337              param,
338              op,
339              IOptionToken(_tk).balanceOf(address(this)), 
340              IOptionToken(_tk).writtenVolume(address(this))
341          );
342          require(
343              op == Operation.BUY ? price >= p : price <= p,
344              "insufficient price"
345          );
346      }
347  
348      function valueOf(address ownr) override public view returns (uint) {
349          (uint bal, int pOut) = getBalanceAndPayout();
350          return uint(int(bal).add(pOut))
351              .mul(balanceOf(ownr)).div(totalSupply());
352      }
353      
354      function writeOptions(
355          address _tk,
356          PricingParameters memory param,
357          uint volume,
358          address to
359      )
360          virtual
361          internal;
362  
363      function calcOptPrice(PricingParameters memory p, Operation op, uint poolPosBuy, uint poolPosSell)
364          virtual
365          internal
366          view
367          returns (uint price);
368  
369      function calcVolume(
370          string memory optSymbol,
371          PricingParameters memory p,
372          uint price,
373          Operation op,
374          uint poolPos
375      )
376          virtual
377          internal
378          view
379          returns (uint volume);
380  
381      function getUdlPrice(address udlFeed) internal view returns (int udlPrice) {
382          (, udlPrice) = UnderlyingFeed(udlFeed).getLatestPrice();
383      }
384  
385      function depositTokensInExchange(address token, uint value) private {
386          IERC20_2 t = IERC20_2(token);
387          t.safeTransferFrom(msg.sender, address(this), value);
388          t.safeApprove(address(exchange), value);
389          exchange.depositTokens(address(this), token, value);
390      }
391  
392      function ensureCaller() private view {
393          require(proposalManager.isRegisteredProposal(msg.sender) && IProposalWrapper(proposalManager.resolve(msg.sender)).isPoolSettingsAllowed(), "proposal not registered/execution not allowed");
394      }
395  }