From 43811e21651465a7326ee5de5ee4e33ce3c67372 Mon Sep 17 00:00:00 2001 From: iamdefinitelyahuman Date: Fri, 14 Jun 2019 18:50:19 +0300 Subject: [PATCH 01/14] waterfall - calculate preferred considerations --- contracts/modules/Waterfall.sol | 135 ++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 contracts/modules/Waterfall.sol diff --git a/contracts/modules/Waterfall.sol b/contracts/modules/Waterfall.sol new file mode 100644 index 0000000..a69abb9 --- /dev/null +++ b/contracts/modules/Waterfall.sol @@ -0,0 +1,135 @@ +pragma solidity >=0.4.24 <0.5.0; + +import "../open-zeppelin/SafeMath.sol"; +import "../interfaces/IToken.sol"; +import "./bases/Module.sol"; + +/** + @title Waterfall Execution Module + @dev Issuer level module for distributing payouts in an exit event + */ +contract WaterfallModule is IssuerModuleBase { + + using SafeMath for uint256; + + string public name = "Waterfall"; + uint256 public mergerConsideration; + uint256 public adjustmentAmount; + + + struct Preferred { + uint256 liquidationPrefPerShare; + IToken token; + bool convertible; + bool participating; + bool senior; + } + + address options; + IToken commonToken; + + Preferred[] preferredTokens; + + /** + @notice Base constructor + @param _owner IssuingEntity contract address + */ + constructor( + address _owner, + IToken _common, + address _options + ) + IssuerModuleBase(_owner) + public + { + _checkToken(_common); + commonToken = _common; + //_checkToken(_options); TODO + options = _options; + + } + + function _checkToken(IToken _token) internal view { + bytes32 _id = _token.ownerID(); + require (_id == ownerID); + require(_token != commonToken); + require (_token != options); // TODO + } + + function addToken( + IToken _token, + uint256 _liquidationPrefPerShare, + bool _convertible, + bool _participating, + bool _senior + ) + external + returns (bool) + { + if (!_onlyAuthority()) return false; + _checkToken(_token); + for (uint256 i; i < preferredTokens.length; i++) { + require(_token != preferredTokens[i].token); + } + preferredTokens.length += 1; + Preferred storage t = preferredTokens[preferredTokens.length-1]; + t.liquidationPrefPerShare = _liquidationPrefPerShare; + t.token = _token; + t.convertible = _convertible; + t.participating = _participating; + t.senior = _senior; + return true; + } + + SeriesConsideration[] considerations; + struct SeriesConsideration { + IToken token; + uint256 dividendAmount; + uint256 totalPerShareConsideration; + } + + function calculateConsiderations( + // uint256 escrowAmount, todo + uint256[] dividendAmounts + ) + external + payable + returns (bool) + { + if (!_onlyAuthority()) return false; + require(dividendAmounts.length == preferredTokens.length); + + uint256 pariPasu = 0; + uint256 total = 0; + uint remaining = address(this).balance; + for (uint256 i = pariPasu; i+1 != 0; i--) { + considerations.length++; + SeriesConsideration storage c = considerations[considerations.length-1]; + + c.token = preferredTokens[i].token; + preferredTokens[i].token.circulatingSupply(); + c.dividendAmount = dividendAmounts[i]; + + uint256 _supply = c.token.circulatingSupply(); + c.totalPerShareConsideration = ( + preferredTokens[i].liquidationPrefPerShare.add(c.dividendAmount.div(_supply)) + ); + total += _supply * c.totalPerShareConsideration; + if (preferredTokens[i].senior) { + if (total > remaining) { + _reduceConsideration(pariPasu, remaining.mul(1000).div(total)); + return true; // rekt + } + pariPasu = considerations.length; + } + } + } + + function _reduceConsideration(uint256 i, uint256 _pct) internal { + for (; i < considerations.length; i++) { + considerations[i].totalPerShareConsideration = considerations[i].totalPerShareConsideration.mul(_pct).div(1000); + } + } + + +} From df3d10a2b4c6cb75e347bdd8db3e7bf38342b12d Mon Sep 17 00:00:00 2001 From: iamdefinitelyahuman Date: Fri, 14 Jun 2019 19:00:29 +0300 Subject: [PATCH 02/14] cleanup --- contracts/modules/Waterfall.sol | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/contracts/modules/Waterfall.sol b/contracts/modules/Waterfall.sol index a69abb9..a5435c6 100644 --- a/contracts/modules/Waterfall.sol +++ b/contracts/modules/Waterfall.sol @@ -103,19 +103,17 @@ contract WaterfallModule is IssuerModuleBase { uint256 total = 0; uint remaining = address(this).balance; for (uint256 i = pariPasu; i+1 != 0; i--) { - considerations.length++; - SeriesConsideration storage c = considerations[considerations.length-1]; - - c.token = preferredTokens[i].token; - preferredTokens[i].token.circulatingSupply(); - c.dividendAmount = dividendAmounts[i]; - - uint256 _supply = c.token.circulatingSupply(); - c.totalPerShareConsideration = ( - preferredTokens[i].liquidationPrefPerShare.add(c.dividendAmount.div(_supply)) - ); - total += _supply * c.totalPerShareConsideration; - if (preferredTokens[i].senior) { + Preferred storage p = preferredTokens[i]; + + uint256 _supply = p.token.circulatingSupply(); + uint256 _prefPerShare = p.liquidationPrefPerShare.add(dividendAmounts[i].div(_supply)); + considerations.push(SeriesConsideration( + p.token, + dividendAmounts[i], + _prefPerShare + )); + total += _supply * _prefPerShare; + if (p.senior) { if (total > remaining) { _reduceConsideration(pariPasu, remaining.mul(1000).div(total)); return true; // rekt From a4dd6ac956a0eccedfda77779db7226af9260401 Mon Sep 17 00:00:00 2001 From: iamdefinitelyahuman Date: Sun, 16 Jun 2019 03:22:46 +0300 Subject: [PATCH 03/14] python implementation for reference (so messy) --- waterfall.py | 208 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 208 insertions(+) create mode 100644 waterfall.py diff --git a/waterfall.py b/waterfall.py new file mode 100644 index 0000000..211765f --- /dev/null +++ b/waterfall.py @@ -0,0 +1,208 @@ +#!/usr/bin/python + +from copy import deepcopy + + +class Series: + + def __init__(self, series, share_quantity, preference, convertible, participating, seniority): + self.series = series + self.share_quantity = share_quantity + self.preference_per_share = preference + self.payout = 0 + self.seniority = seniority + + self.participating = participating + if participating or not convertible: + self.converts = False + else: + self.can_convert = convertible + self.converts = None + self.participating = None + + def __bool__(self): + return self.converts is not None + + def __repr__(self): + if self.converts: + return ( + f"" + ) + return ( + f"" + ) + + def preference_total(self): + return self.preference_per_share * self.share_quantity + + def would_convert(self, payout_total, common_total, options, junior): + if self.converts is not None: + if not junior: + return [self.converts] + return [self.converts] + junior[0].would_convert( + payout_total, + common_total, + options, + junior[1:] + ) + + if not junior: + payout_total += self.payout + common_total += self.share_quantity + if payout_total / common_total * self.share_quantity < self.payout: + return [False] + payout_adjusted, common_adjusted, _ = adjust_options( + payout_total, + common_total, + options + ) + return [payout_adjusted / common_adjusted * self.share_quantity > self.payout] + + convert_results = junior[0].would_convert( + payout_total + self.payout, + common_total + self.share_quantity, + options, + junior[1:] + ) + adjusted_common, adjusted_payout = common_total, payout_total + for i, p in zip(convert_results, junior): + if i: + adjusted_common += p.share_quantity + adjusted_payout += p.payout + + if adjusted_payout / adjusted_common * self.share_quantity < self.payout: + return [False] + junior[0].would_convert( + payout_total, + common_total, + options, + junior[1:] + ) + adjusted_payout, adjusted_common, _ = adjust_options( + adjusted_payout, + adjusted_common, + options + ) + if adjusted_payout / adjusted_common * self.share_quantity > self.payout: + return [True] + convert_results + return [False] + junior[0].would_convert(payout_total, common_total, options, junior[1:]) + + def set_payout(self, per_share): + payout = self.share_quantity * per_share + self.payout += payout + return payout + + +class Option: + + def __init__(self, quantity, call_value): + self.quantity = quantity + self.call_value = call_value + + def in_the_money(self, common_total, payout): + return payout / (self.quantity + common_total) > self.call_value + + def total(self): + return self.call_value * self.quantity + + def adjusted_payout(self, per_share): + return (per_share - self.call_value) * self.quantity + + def set_payout(self, per_share): + self.payout = (per_share - self.call_value) * self.quantity + return self.payout + + def __repr__(self): + return ( + f"