GovernableLiquidityPoolV2.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/ICreditProvider.sol"; 11 import "../interfaces/IProtocolSettings.sol"; 12 import "../interfaces/IProposalWrapper.sol"; 13 import "../interfaces/IProposalManager.sol"; 14 import "../interfaces/IInterpolator.sol"; 15 import "../interfaces/IBaseHedgingManager.sol"; 16 import "../finance/RedeemableToken.sol"; 17 import "../utils/SafeERC20.sol"; 18 import "../utils/SafeCast.sol"; 19 import "../utils/MoreMath.sol"; 20 import "../utils/SignedSafeMath.sol"; 21 22 abstract contract GovernableLiquidityPoolV2 is ManagedContract, RedeemableToken, IGovernableLiquidityPool { 23 24 using SafeERC20 for IERC20_2; 25 using SafeCast for uint; 26 using SafeMath for uint; 27 using SignedSafeMath for int; 28 29 string internal _name; 30 string internal _symbol; 31 string internal constant _symbol_prefix = "DODv2-LLPRTK-"; 32 string internal constant _name_prefix = "Linear Liquidity Pool Redeemable Token: "; 33 34 IYieldTracker private tracker; 35 IProtocolSettings private settings; 36 IInterpolator internal interpolator; 37 ICreditProvider private creditProvider; 38 IProposalManager private proposalManager; 39 40 mapping(string => PricingParameters) private parameters; 41 mapping(string => mapping(uint => Range)) private ranges; 42 43 uint public override maturity; 44 uint public override withdrawFee; 45 uint internal volumeBase = 1e18; 46 uint internal reserveRatio; 47 uint internal fractionBase; 48 uint internal _leverageMultiplier; 49 uint internal _hedgeThreshold; 50 51 address private _hedgingManagerAddress; 52 bool private onlyMintToOwner; 53 54 string[] private optSymbols; 55 56 constructor(string memory _nm, string memory _sb, address _deployAddr, bool _onlyMintToOwner, address _owner) 57 ERC20(string(abi.encodePacked(_name_prefix, _nm))) 58 public 59 { 60 _symbol = _sb; 61 _name = _nm; 62 63 fractionBase = 1e9; 64 exchange = IOptionsExchange(Deployer(_deployAddr).getContractAddress("OptionsExchange")); 65 settings = IProtocolSettings(Deployer(_deployAddr).getContractAddress("ProtocolSettings")); 66 creditProvider = ICreditProvider(Deployer(_deployAddr).getContractAddress("CreditProvider")); 67 tracker = IYieldTracker(Deployer(_deployAddr).getContractAddress("YieldTracker")); 68 interpolator = IInterpolator(Deployer(_deployAddr).getContractAddress("Interpolator")); 69 proposalManager = IProposalManager(Deployer(_deployAddr).getContractAddress("ProposalsManager")); 70 owner = _owner; 71 onlyMintToOwner = _onlyMintToOwner; 72 } 73 74 function setParameters( 75 uint _reserveRatio, 76 uint _withdrawFee, 77 uint _mt, 78 uint _lm, 79 address _hmngr, 80 uint _ht 81 ) 82 override external 83 { 84 ensureCaller(); 85 reserveRatio = _reserveRatio; 86 withdrawFee = _withdrawFee; 87 maturity = _mt; 88 _leverageMultiplier = _lm; 89 _hedgingManagerAddress = _hmngr; 90 _hedgeThreshold = _ht; 91 } 92 93 function getHedgingManager() override public view returns (address) { 94 return _hedgingManagerAddress; 95 } 96 97 function getHedgeNotionalThreshold() override external view returns (uint) { 98 return _hedgeThreshold; 99 } 100 101 function getLeverage() override public view returns (uint) { 102 return _leverageMultiplier; 103 } 104 105 function redeemAllowed() override public view returns (bool) { 106 107 return block.timestamp >= maturity; //FOR DEPLOYMENTS 108 //return settings.exchangeTime() >= maturity;//FOR TESTS 109 } 110 111 function yield(uint dt) override external view returns (uint) { 112 return tracker.yield(address(this), dt); 113 } 114 115 function addSymbol( 116 address udlFeed, 117 uint strike, 118 uint _mt, 119 IOptionsExchange.OptionType optType, 120 uint t0, 121 uint t1, 122 uint120[] calldata x, 123 uint120[] calldata y, 124 uint[3] calldata bsStockSpread 125 ) 126 override external 127 { 128 ensureCaller(); 129 require(x.length > 0 && x.length.mul(2) == y.length && _mt < maturity, "bad x/y or _mt"); 130 131 string memory optSymbol = exchange.getOptionSymbol( 132 IOptionsExchange.OptionData(udlFeed, optType, strike.toUint120(), _mt.toUint32()) 133 ); 134 135 if (parameters[optSymbol].x.length == 0) { 136 optSymbols.push(optSymbol); 137 } 138 139 parameters[optSymbol] = PricingParameters( 140 udlFeed, 141 optType, 142 strike.toUint120(), 143 _mt.toUint32(), 144 t0.toUint32(), 145 t1.toUint32(), 146 bsStockSpread, 147 x, 148 y 149 ); 150 151 emit AddSymbol(optSymbol); 152 } 153 154 function setRange(string calldata optSymbol, Operation op, uint start, uint end) external { 155 ensureCaller(); 156 ranges[optSymbol][uint(op)] = Range(start.toUint120(), end.toUint120()); 157 } 158 159 function removeSymbol(string calldata optSymbol) external { 160 require(parameters[optSymbol].maturity >= block.timestamp, "2 soon"); 161 Arrays.removeItem(optSymbols, optSymbol); 162 } 163 164 function depositTokens(address to, address token, uint value) override public { 165 166 if (onlyMintToOwner) { 167 require(to == owner, "bad ownr"); 168 } 169 170 (uint b0, int po) = getBalanceAndPayout(); 171 depositTokensInExchange(token, value); 172 uint b1 = exchange.balanceOf(address(this)); 173 174 //tracker.push(int(b0).add(po), b1.sub(b0).toInt256()); 175 176 int expBal = po.add(int(b1)); 177 uint p = b1.sub(b0).mul(fractionBase).div(uint(expBal)); 178 179 uint b = 1e3; 180 uint v = _totalSupply > 0 ? 181 _totalSupply.mul(p).mul(b).div(fractionBase.sub(p)) : 182 uint(expBal).mul(b); 183 v = MoreMath.round(v, b); 184 185 addBalance(to, v); 186 _totalSupply = _totalSupply.add(v); 187 emitTransfer(address(0), to, v); 188 } 189 190 function withdraw(uint amount) override external { 191 192 uint bal = balanceOf(msg.sender); 193 require(bal >= amount, "low bal"); 194 195 uint val = valueOf(msg.sender).mul(amount).div(bal); 196 uint discountedValue = val.mul(fractionBase.sub(withdrawFee)).div(fractionBase); 197 uint freeBal = calcFreeBalance(); 198 199 if (freeBal > 0) { 200 //(uint b0, int po) = getBalanceAndPayout(); 201 202 exchange.transferBalance( 203 msg.sender, 204 (discountedValue <= freeBal) ? discountedValue : freeBal 205 ); 206 207 /*tracker.push( 208 int(b0).add(po), 209 -(((discountedValue <= freeBal) ? val : freeBal).toInt256()) 210 );*/ 211 } 212 213 removeBalance(msg.sender, amount); 214 _totalSupply = _totalSupply.sub(amount); 215 emitTransfer(msg.sender, address(0), amount); 216 } 217 218 function calcFreeBalance() override public view returns (uint balance) { 219 //used for pool deposits/withdrawls of pool tokens 220 uint exBal = exchange.balanceOf(address(this)); 221 uint reserve = exBal.mul(reserveRatio).div(fractionBase); 222 uint sp = exBal.sub(exchange.collateral(address(this))); 223 balance = sp > reserve ? sp.sub(reserve) : 0; 224 } 225 226 function calcFreeTradableBalance() internal view returns (uint balance) { 227 //used for calcing what traders can trade against 228 uint exBal = settings.getPoolCreditTradeable(address(this)); 229 uint reserve = exBal.mul(reserveRatio).div(fractionBase); 230 uint sp = exBal.sub(exchange.collateral(address(this))); 231 balance = sp > reserve ? sp.sub(reserve) : 0; 232 } 233 234 function listSymbols() override external view returns (string memory available) { 235 for (uint i = 0; i < optSymbols.length; i++) { 236 if (bytes(available).length == 0) { 237 available = optSymbols[i]; 238 } else { 239 available = string(abi.encodePacked(available, "\n", optSymbols[i])); 240 } 241 } 242 } 243 244 function queryBuy(string memory optSymbol, bool isBuy) 245 override 246 public 247 view 248 returns (uint price, uint volume) 249 { 250 251 Operation op = (isBuy == true) ? Operation.BUY : Operation.SELL; 252 253 PricingParameters memory param = parameters[optSymbol]; 254 address _tk = exchange.resolveToken(optSymbol); 255 uint optBal = (op == Operation.SELL) ? IOptionToken(_tk).balanceOf(address(this)) : IOptionToken(_tk).writtenVolume(address(this)); 256 price = calcOptPrice(param, op, IOptionToken(_tk).balanceOf(address(this)), IOptionToken(_tk).writtenVolume(address(this))); 257 volume = MoreMath.min( 258 calcVolume(optSymbol, param, price, op, 0), 259 (op == Operation.SELL) ? ( 260 (param.bsStockSpread[1] >= optBal) ? param.bsStockSpread[1].sub(optBal) : 0 261 ) : ( 262 (param.bsStockSpread[0] >= optBal) ? param.bsStockSpread[0].sub(optBal) : 0 263 ) 264 ); 265 } 266 267 function buy(string memory optSymbol, uint price, uint volume, address token) 268 override 269 public 270 returns (address _tk) 271 { 272 PricingParameters memory param; 273 _tk = exchange.resolveToken(optSymbol); 274 275 (price, param) = validateOrder(volume, price, optSymbol, Operation.BUY, _tk); 276 checkApproved(param.udlFeed); 277 278 uint value = price.mul(volume).div(volumeBase); 279 if (token != address(exchange)) { 280 (uint tv, uint tb) = settings.getTokenRate(token); 281 value = value.mul(tv).div(tb); 282 depositTokensInExchange(token, value); 283 } else { 284 exchange.transferBalance(msg.sender, address(this), value); 285 } 286 287 288 if (volume > IOptionToken(_tk).balanceOf(address(this))) { 289 // only credit the amount excess what is already available 290 uint freeBal = calcFreeBalance(); 291 if (value > freeBal){ 292 creditProvider.borrowBuyLiquidity(address(this), value.sub(freeBal), _tk); 293 } 294 writeOptions(_tk, param, volume, msg.sender); 295 } else { 296 IOptionToken(_tk).transfer(msg.sender, volume); 297 } 298 299 hedge(param); 300 301 emit Buy(_tk, msg.sender, price, volume); 302 } 303 304 function sell( 305 string memory optSymbol, 306 uint price, 307 uint volume 308 ) 309 override 310 public 311 { 312 PricingParameters memory param; 313 address _tk = exchange.resolveToken(optSymbol); 314 (price, param) = validateOrder(volume, price, optSymbol, Operation.SELL, _tk); 315 checkApproved(param.udlFeed); 316 317 IOptionToken(_tk).transferFrom(msg.sender, address(this), volume); 318 319 uint _written = IOptionToken(_tk).writtenVolume(address(this)); 320 if (_written > 0) { 321 IOptionToken(_tk).burn( 322 MoreMath.min(_written, volume) 323 ); 324 } 325 326 uint value = price.mul(volume).div(volumeBase); 327 uint freeBal = calcFreeBalance(); 328 if (freeBal < value) { 329 // only credit the amount excess what is already available, will fail if hedging manager not approved 330 creditProvider.borrowSellLiquidity(address(this), value.sub(freeBal), _tk); 331 } 332 333 exchange.transferBalance(msg.sender, value); 334 // holding <= sellStock 335 require(calcFreeBalance() > 0 && IOptionToken(_tk).balanceOf(address(this)) <= param.bsStockSpread[1], "bal 2 low/high volume"); 336 337 hedge(param); 338 339 emit Sell(_tk, msg.sender, price, volume); 340 } 341 342 function getBalanceAndPayout() private view returns (uint bal, int pOut) { 343 344 bal = exchange.balanceOf(address(this)); 345 pOut = exchange.calcExpectedPayout(address(this)); 346 } 347 348 function hedge(PricingParameters memory param) private { 349 //trigger hedge, may need to factor in costs and charge it to msg.sender 350 if (_hedgingManagerAddress != address(0)) { 351 IBaseHedgingManager(_hedgingManagerAddress).balanceExposure( 352 param.udlFeed 353 ); 354 } 355 } 356 357 function isInRange( 358 string memory optSymbol, 359 Operation op, 360 address udlFeed 361 ) 362 private 363 view 364 returns(bool) 365 { 366 Range memory r = ranges[optSymbol][uint(op)]; 367 if (r.start == 0 && r.end == 0) { 368 return true; 369 } 370 int udlPrice = getUdlPrice(udlFeed); 371 return uint(udlPrice) >= r.start && uint(udlPrice) <= r.end; 372 } 373 374 function validateOrder( 375 uint volume, 376 uint price, 377 string memory optSymbol, 378 Operation op, 379 address _tk 380 ) 381 private 382 view 383 returns (uint p, PricingParameters memory param) 384 { 385 param = parameters[optSymbol]; 386 require(volume > 0 && isInRange(optSymbol, op, param.udlFeed), "bad rng or vol"); 387 p = calcOptPrice( 388 param, 389 op, 390 IOptionToken(_tk).balanceOf(address(this)), 391 IOptionToken(_tk).writtenVolume(address(this)) 392 ); 393 require( 394 op == Operation.BUY ? price >= p : price <= p, 395 "bad price" 396 ); 397 } 398 399 function valueOf(address ownr) override public view returns (uint) { 400 (uint bal, int pOut) = getBalanceAndPayout(); 401 return uint(int(bal).add(pOut)) 402 .mul(balanceOf(ownr)).div(totalSupply()); 403 } 404 405 function writeOptions( 406 address _tk, 407 PricingParameters memory param, 408 uint volume, 409 address to 410 ) 411 virtual 412 internal; 413 414 function calcOptPrice(PricingParameters memory p, Operation op, uint poolPosBuy, uint poolPosSell) 415 virtual 416 internal 417 view 418 returns (uint price); 419 420 function calcVolume( 421 string memory optSymbol, 422 PricingParameters memory p, 423 uint price, 424 Operation op, 425 uint poolPos 426 ) 427 virtual 428 internal 429 view 430 returns (uint volume); 431 432 function getUdlPrice(address udlFeed) internal view returns (int udlPrice) { 433 (, udlPrice) = UnderlyingFeed(udlFeed).getLatestPrice(); 434 } 435 436 function depositTokensInExchange(address token, uint value) private { 437 IERC20_2(token).safeTransferFrom(msg.sender, address(this), value); 438 IERC20_2(token).safeApprove(address(exchange), value); 439 exchange.depositTokens(address(this), token, value); 440 } 441 442 function ensureCaller() private view { 443 require(proposalManager.isRegisteredProposal(msg.sender) && IProposalWrapper(proposalManager.resolve(msg.sender)).isPoolSettingsAllowed(), "ONG"); 444 } 445 446 function checkApproved(address udlFeed) private view { 447 address ppk = UnderlyingFeed(udlFeed).getPrivledgedPublisherKeeper(); 448 if (ppk != address(0)) { 449 require(ppk == tx.origin, "not approved"); 450 } 451 } 452 }