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 }