From cbde5d896e1367341a9357a65e0cfeea2d8f82d4 Mon Sep 17 00:00:00 2001 From: cinquemb <1519017+cinquemb@users.noreply.github.com> Date: Wed, 30 Dec 2020 15:58:28 -0500 Subject: [PATCH 01/47] first shot at premium decay of coupon with longer expiry --- protocol/contracts/dao/Market.sol | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/protocol/contracts/dao/Market.sol b/protocol/contracts/dao/Market.sol index 2740d674..37b9477c 100644 --- a/protocol/contracts/dao/Market.sol +++ b/protocol/contracts/dao/Market.sol @@ -18,6 +18,7 @@ pragma solidity ^0.5.17; pragma experimental ABIEncoderV2; import "@openzeppelin/contracts/math/SafeMath.sol"; +import "@abdk-libraries-solidity/ABDKMath64x64.sol"; import "./Curve.sol"; import "./Comptroller.sol"; import "../Constants.sol"; @@ -65,7 +66,7 @@ contract Market is Comptroller, Curve { return calculateCouponPremium(dollar().totalSupply(), totalDebt(), amount); } - function purchaseCoupons(uint256 dollarAmount) external returns (uint256) { + function purchaseCoupons(uint256 couponEpoch, uint256 dollarAmount) external returns (uint256) { Require.that( dollarAmount > 0, FILE, @@ -78,8 +79,17 @@ contract Market is Comptroller, Curve { "Not enough debt" ); - uint256 epoch = epoch(); - uint256 couponAmount = dollarAmount.add(couponPremium(dollarAmount)); + uint256 epochMultiplier = Decimal.one().div(couponEpoch, Constants.getCouponExpiration()); + uint256 defaultPremium = couponPremium(dollarAmount); + uit256 discountedPremium = defaultPremium.div( + uit256( + ln(math.e + epochMultiplier) + ).pow(2) + ); + uint256 actualEpoch = epochMultiplier.mul(Constants.getCouponExpiration()); + uint256 epoch = epoch().add(actualEpoch); + + uint256 couponAmount = dollarAmount.add(discountedPremium); burnFromAccount(msg.sender, dollarAmount); incrementBalanceOfCoupons(msg.sender, epoch, couponAmount); From 6c19be461a39c23e050b29cdbc6b72f22d3b0d53 Mon Sep 17 00:00:00 2001 From: cinquemb <1519017+cinquemb@users.noreply.github.com> Date: Wed, 30 Dec 2020 22:54:55 -0500 Subject: [PATCH 02/47] updates for eulers number and missing import --- protocol/contracts/dao/Market.sol | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/protocol/contracts/dao/Market.sol b/protocol/contracts/dao/Market.sol index 37b9477c..298f0c66 100644 --- a/protocol/contracts/dao/Market.sol +++ b/protocol/contracts/dao/Market.sol @@ -22,18 +22,25 @@ import "@abdk-libraries-solidity/ABDKMath64x64.sol"; import "./Curve.sol"; import "./Comptroller.sol"; import "../Constants.sol"; +import "../external/Decimal.sol"; contract Market is Comptroller, Curve { using SafeMath for uint256; bytes32 private constant FILE = "Market"; + /* Euler's number */ + uint256 private constant eN = 115792089237316195423570985008687907853269984664; + uint256 private constant eD = 42597529080697662913911602080600932014987715856; + uint256 private constant e = eN.div(eD); + event CouponExpiration(uint256 indexed epoch, uint256 couponsExpired, uint256 lessRedeemable, uint256 lessDebt, uint256 newBonded); event CouponPurchase(address indexed account, uint256 indexed epoch, uint256 dollarAmount, uint256 couponAmount); event CouponRedemption(address indexed account, uint256 indexed epoch, uint256 couponAmount); event CouponTransfer(address indexed from, address indexed to, uint256 indexed epoch, uint256 value); event CouponApproval(address indexed owner, address indexed spender, uint256 value); + function step() internal { // Expire prior coupons for (uint256 i = 0; i < expiringCoupons(epoch()); i++) { @@ -83,7 +90,7 @@ contract Market is Comptroller, Curve { uint256 defaultPremium = couponPremium(dollarAmount); uit256 discountedPremium = defaultPremium.div( uit256( - ln(math.e + epochMultiplier) + ln(e + epochMultiplier) ).pow(2) ); uint256 actualEpoch = epochMultiplier.mul(Constants.getCouponExpiration()); From 4b1984391344d455d8832c3bb2d27723f60cb6f6 Mon Sep 17 00:00:00 2001 From: cinquemb <1519017+cinquemb@users.noreply.github.com> Date: Thu, 31 Dec 2020 06:55:18 -0500 Subject: [PATCH 03/47] fix multiplier --- protocol/contracts/dao/Market.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/protocol/contracts/dao/Market.sol b/protocol/contracts/dao/Market.sol index 298f0c66..1cc8ec07 100644 --- a/protocol/contracts/dao/Market.sol +++ b/protocol/contracts/dao/Market.sol @@ -22,7 +22,6 @@ import "@abdk-libraries-solidity/ABDKMath64x64.sol"; import "./Curve.sol"; import "./Comptroller.sol"; import "../Constants.sol"; -import "../external/Decimal.sol"; contract Market is Comptroller, Curve { using SafeMath for uint256; @@ -86,7 +85,7 @@ contract Market is Comptroller, Curve { "Not enough debt" ); - uint256 epochMultiplier = Decimal.one().div(couponEpoch, Constants.getCouponExpiration()); + uint256 epochMultiplier = couponEpoch.div(Constants.getCouponExpiration()); uint256 defaultPremium = couponPremium(dollarAmount); uit256 discountedPremium = defaultPremium.div( uit256( From 42de4ef9284ae0bd9d809777f91365d98ff282fe Mon Sep 17 00:00:00 2001 From: cinquemb <1519017+cinquemb@users.noreply.github.com> Date: Thu, 31 Dec 2020 06:56:39 -0500 Subject: [PATCH 04/47] fix uint256 assignment --- protocol/contracts/dao/Market.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocol/contracts/dao/Market.sol b/protocol/contracts/dao/Market.sol index 1cc8ec07..3d85e371 100644 --- a/protocol/contracts/dao/Market.sol +++ b/protocol/contracts/dao/Market.sol @@ -87,7 +87,7 @@ contract Market is Comptroller, Curve { uint256 epochMultiplier = couponEpoch.div(Constants.getCouponExpiration()); uint256 defaultPremium = couponPremium(dollarAmount); - uit256 discountedPremium = defaultPremium.div( + uint256 discountedPremium = defaultPremium.div( uit256( ln(e + epochMultiplier) ).pow(2) From 1a4b15feb2bd8f21394e2a64408fd2a16453e854 Mon Sep 17 00:00:00 2001 From: cinquemb <1519017+cinquemb@users.noreply.github.com> Date: Sun, 3 Jan 2021 01:36:12 -0500 Subject: [PATCH 05/47] first stab at coupon auction --- protocol/contracts/dao/Auction.sol | 85 ++++++++++++++++++++++++++++ protocol/contracts/dao/Getters.sol | 8 +++ protocol/contracts/dao/Market.sol | 32 ++++++++++- protocol/contracts/dao/Regulator.sol | 5 ++ protocol/contracts/dao/Setters.sol | 50 ++++++++++++++++ protocol/contracts/dao/State.sol | 23 +++++++- 6 files changed, 200 insertions(+), 3 deletions(-) create mode 100644 protocol/contracts/dao/Auction.sol diff --git a/protocol/contracts/dao/Auction.sol b/protocol/contracts/dao/Auction.sol new file mode 100644 index 00000000..c13b8a29 --- /dev/null +++ b/protocol/contracts/dao/Auction.sol @@ -0,0 +1,85 @@ +/* + Copyright 2020 Empty Set Squad + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +pragma solidity ^0.5.17; +pragma experimental ABIEncoderV2; + +import "@openzeppelin/contracts/math/SafeMath.sol"; +import "./Setters.sol"; +import "../external/Require.sol"; + +contract Auction is Setters { + using SafeMath for uint256; + bytes32 private constant FILE = "Auction"; + + struct BidDistance { + uint256 distance; + } + + //event CouponBidWithdraw(address withdrawer, address withdrawalAccount, uint amount); + event CouponAuctionCanceled(); + event CouponAuctionSettled(); + + function createCouponAuction() internal { + Auction newAuction = new Auction(this); + newAuction._totalBids = 0; + newAuction.minMaturity = 1000000000000000000000000; + newAuction.maxMaturity = 0; + newAuction.minYield = 1000000000000000000000000; + newAuction.maxYield = 0; + setAuction(newAuction); + CouponAuctionCreated(newAuction); + } + + function cancelCouponAuction() internal returns (bool success) { + // can only cancel previous auction when in next epoch + cancelAuction(epoch() - 1); + emit CouponAuctionCanceled(); + return true; + } + + /* TODO: in the begining of the next epoch, all the available best bids that meet the specification will have their funds withdraw and coupons orders placed for the amount, premium, and maturity up to the amount of debt avaiable, the rest will have their funds sent back to them and */ + function settleCouponAuction() internal returns (bool success) { + if (!isCouponAuctionFinished() && !isCouponAuctionCanceled){ + // loop over bids and compute distance from best(yield, maturity) + uint256 minMaturity = getMinMaturity(); + uint256 maxMaturity = getMaxMaturity(); + uint256 minYield = getMinYield(); + uint256 maxYield = getMaxYield(); + + + for(uint256 i = 0 ; i < getCouponAuctionBids(); i++) { + + uint256 couponMaturityEpoch = getCouponBidderState(getCouponBidderAtIndex(i)).couponMaturityEpoch; + uint256 couponAmount = getCouponBidderState(getCouponBidderAtIndex(i)).couponAmount; + uint256 dollarAmount = getCouponBidderState(getCouponBidderAtIndex(i)).dollarAmount; + + uint256 yieldRel = dollarAmount.div( + couponAmount + ).div( + maxYield.sub(minYield) + ); + uint256 maturityRel = couponMaturityEpoch.div( + maxMaturity.sub(minMaturity) + ); + + uint256 sumSquared = yieldRel.pow(2) + maturityRel.pow(2); + } + } + + return true; + } +} \ No newline at end of file diff --git a/protocol/contracts/dao/Getters.sol b/protocol/contracts/dao/Getters.sol index 46498b8a..71a73f37 100644 --- a/protocol/contracts/dao/Getters.sol +++ b/protocol/contracts/dao/Getters.sol @@ -191,6 +191,14 @@ contract Getters is State { return epoch <= Constants.getBootstrappingPeriod(); } + function getCouponAuctionAtEpoch(uint256 epoch) internal returns (Auction){ + return _state.epochs[epoch].auction; + } + + function getCouponAuctionBids() internal returns (uint256){ + return _state.epochs[epoch].auction._totalBids; + } + /** * Governance */ diff --git a/protocol/contracts/dao/Market.sol b/protocol/contracts/dao/Market.sol index 3d85e371..00a11b71 100644 --- a/protocol/contracts/dao/Market.sol +++ b/protocol/contracts/dao/Market.sol @@ -19,6 +19,7 @@ pragma experimental ABIEncoderV2; import "@openzeppelin/contracts/math/SafeMath.sol"; import "@abdk-libraries-solidity/ABDKMath64x64.sol"; +import "./Auction.sol"; import "./Curve.sol"; import "./Comptroller.sol"; import "../Constants.sol"; @@ -38,8 +39,7 @@ contract Market is Comptroller, Curve { event CouponRedemption(address indexed account, uint256 indexed epoch, uint256 couponAmount); event CouponTransfer(address indexed from, address indexed to, uint256 indexed epoch, uint256 value); event CouponApproval(address indexed owner, address indexed spender, uint256 value); - - + function step() internal { // Expire prior coupons for (uint256 i = 0; i < expiringCoupons(epoch()); i++) { @@ -133,4 +133,32 @@ contract Market is Comptroller, Curve { emit CouponTransfer(sender, recipient, epoch, amount); } + + function placeCouponAuctionBid(uint256 couponEpochExpiry, uint256 dollarAmount, uint256 maxCouponAmount) returns external (bool success) { + /* TODO: + Need to select best based on price (lowest yield / maxCouponAmount) + Need to select based on shortest maturity (1 epoch min) + */ + + // reject coupon amounts of 0 + Require.that( + maxCouponAmount > 0, + FILE, + "Your coupon amount is too low" + ); + + // reject payments of 0 + Require.that( + dollarAmount > 0, + FILE, + "Your price is too low" + ); + + setRelYield(dollarAmount.div(maxCouponAmount)); + setRelMaturity(couponEpochExpiry); + setCouponBidderState(msg.sender, couponEpochExpiry, dollarAmount, maxCouponAmount); + setCouponBidderStateIndex(getCouponAuctionBidIndex(), msg.sender); + incrementCouponAuctionBids(); + return true; + } } diff --git a/protocol/contracts/dao/Regulator.sol b/protocol/contracts/dao/Regulator.sol index 5920424c..4d84f58a 100644 --- a/protocol/contracts/dao/Regulator.sol +++ b/protocol/contracts/dao/Regulator.sol @@ -34,11 +34,16 @@ contract Regulator is Comptroller { Decimal.D256 memory price = oracleCapture(); if (price.greaterThan(Decimal.one())) { + /* TODO: Cancel any outstanding previous coupon auction */ + growSupply(price); return; } if (price.lessThan(Decimal.one())) { + /* TODO: If any outstanding previous auction, fill acceptable bids for coupons, if none cancel previous auction */ + + /* TODO: Launch new auction for this epoch */ shrinkSupply(price); return; } diff --git a/protocol/contracts/dao/Setters.sol b/protocol/contracts/dao/Setters.sol index 8ca83c92..c5d2b2ae 100644 --- a/protocol/contracts/dao/Setters.sol +++ b/protocol/contracts/dao/Setters.sol @@ -149,6 +149,56 @@ contract Setters is State, Getters { _state.epochs[epoch].coupons.outstanding = 0; } + function setCouponAuction(Auction auction) internal { + if(!_state.epochs[epoch()].auction) { + _state.epochs[epoch()].auction = auction; + } + } + + function cancelCounponAuction(uint256 epoch) internal { + if ((epoch() - 1) == epoch) { + _state.epochs[epoch].auction.canceled = true; + } + } + + function settleCouponAuction(uint256 epoch) internal { + if ((epoch() - 1) == epoch) { + _state.epochs[epoch].auction.finished = true; + } + } + + function setCouponBidderState(address bidder, uint256 couponEpochExpiry, uint256 dollarAmount, uint256 maxCouponAmount) internal { + CouponBidderState storage bidderState = _state.epochs[epoch()].auction.couponBidderState[bidder]; + + bidderState.couponMaturityEpoch = couponEpochExpiry; // revisit this + bidderState.dollarAmount = dollarAmount; + bidderState.couponAmount = maxCouponAmount; + } + + function setCouponBidderStateIndex(uint256 index, address bidder) internal { + _state.epochs[epoch()].auction.couponBidder[index] = bidder; + } + + function incrementCouponAuctionBids() internal { + _state.epochs[epoch()].auction._totalBids++; + } + + function setRelYield(uint256 yield) internal { + if (yield > _state.epochs[epoch()].auction.maxYield) { + _state.epochs[epoch()].auction.maxYield = yield; + } else if (yield < _state.epochs[epoch()].auction.minYield) { + _state.epochs[epoch()].auction.minYield = yield; + } + } + setRelMaturity(uint256 couponEpochExpiry) internal { + if (couponEpochExpiry > _state.epochs[epoch()].auction.maxMaturity) { + _state.epochs[epoch()].auction.maxMaturity = couponEpochExpiry; + } else if (couponEpochExpiry < _state.epochs[epoch()].auction.minMaturity) { + _state.epochs[epoch()].auction.minMaturity = couponEpochExpiry; + } + } + + /** * Governance */ diff --git a/protocol/contracts/dao/State.sol b/protocol/contracts/dao/State.sol index 50888867..3189a9ab 100644 --- a/protocol/contracts/dao/State.sol +++ b/protocol/contracts/dao/State.sol @@ -52,10 +52,31 @@ contract Epoch { uint256[] expiring; } + struct CouponBidderState { + uint256 couponMaturityEpoch; + uint256 dollarAmount; + uint256 couponAmount; + bool selected; + bool rejected; + } + + struct Auction { + bool canceled; + bool finished; + uint256 minMaturity; + uint256 maxMaturity; + uint256 minYield; + uint256 maxYield; + uint256 _totalBids; + mapping(uint256 => address) couponBidder; + mapping(address => CouponBidderState) couponBidderState; + } + struct State { uint256 bonded; Coupons coupons; - } + Auction auction; + } } contract Candidate { From f41d9588dd0291d8146477fa43a6d60cedae1b9e Mon Sep 17 00:00:00 2001 From: cinquemb <1519017+cinquemb@users.noreply.github.com> Date: Sun, 3 Jan 2021 04:11:28 -0500 Subject: [PATCH 06/47] sort bids by euclidan distance with equal weights on min bid and min maturity --- protocol/contracts/dao/Auction.sol | 57 +++++++++++++++++++++++++----- protocol/contracts/dao/Setters.sol | 2 +- protocol/contracts/dao/State.sol | 1 + 3 files changed, 51 insertions(+), 9 deletions(-) diff --git a/protocol/contracts/dao/Auction.sol b/protocol/contracts/dao/Auction.sol index c13b8a29..7e9bba5d 100644 --- a/protocol/contracts/dao/Auction.sol +++ b/protocol/contracts/dao/Auction.sol @@ -25,14 +25,44 @@ contract Auction is Setters { using SafeMath for uint256; bytes32 private constant FILE = "Auction"; - struct BidDistance { - uint256 distance; - } - - //event CouponBidWithdraw(address withdrawer, address withdrawalAccount, uint amount); + event CouponAuctionCreated(); event CouponAuctionCanceled(); event CouponAuctionSettled(); + function sortBidsByDistance(Epoch.CouponBidderState[] bids) public constant internal returns(Epoch.CouponBidderState[]) { + quickSort(bids, uint256(0), uint256(bids.length - 1)); + return bids; + } + + function quickSort(Epoch.CouponBidderState[] memory arr, uint256 left, uint256 right) internal { + uint256 i = left; + uint256 j = right; + if(i==j) return; + uint256 pivot = arr[uint256(left + (right - left) / 2)]; + while (i <= j) { + while (arr[uint256(i)] < pivot) i++; + while (pivot < arr[uint256(j)]) j--; + if (i <= j) { + (arr[uint256(i)], arr[uint256(j)]) = (arr[uint256(j)], arr[uint256(i)]); + i++; + j--; + } + } + if (left < j) + quickSort(arr, left, j); + if (i < right) + quickSort(arr, i, right); + } + + function sqrt(uint256 x) internal returns (uint256 y) { + uint256 z = x.add(1).div(2); + y = x; + while (z < y) { + y = z; + z = x.div(z.add(z)).div(2); + } + } + function createCouponAuction() internal { Auction newAuction = new Auction(this); newAuction._totalBids = 0; @@ -54,15 +84,16 @@ contract Auction is Setters { /* TODO: in the begining of the next epoch, all the available best bids that meet the specification will have their funds withdraw and coupons orders placed for the amount, premium, and maturity up to the amount of debt avaiable, the rest will have their funds sent back to them and */ function settleCouponAuction() internal returns (bool success) { if (!isCouponAuctionFinished() && !isCouponAuctionCanceled){ - // loop over bids and compute distance from best(yield, maturity) + uint256 minMaturity = getMinMaturity(); uint256 maxMaturity = getMaxMaturity(); uint256 minYield = getMinYield(); uint256 maxYield = getMaxYield(); + + Epoch.CouponBidderState[] memory bids; - + // loop over bids and compute distance for(uint256 i = 0 ; i < getCouponAuctionBids(); i++) { - uint256 couponMaturityEpoch = getCouponBidderState(getCouponBidderAtIndex(i)).couponMaturityEpoch; uint256 couponAmount = getCouponBidderState(getCouponBidderAtIndex(i)).couponAmount; uint256 dollarAmount = getCouponBidderState(getCouponBidderAtIndex(i)).dollarAmount; @@ -77,7 +108,17 @@ contract Auction is Setters { ); uint256 sumSquared = yieldRel.pow(2) + maturityRel.pow(2); + uint256 distance = sqrt(sumSquared); + getCouponBidderState(getCouponBidderAtIndex(i)).distance = distance; + + bids.push(getCouponBidderState(getCouponBidderAtIndex(i)).distance); } + + // sort bids + sortBidsByDistance(bids); + + // assign coupons untill filled, reject the rest + } return true; diff --git a/protocol/contracts/dao/Setters.sol b/protocol/contracts/dao/Setters.sol index c5d2b2ae..1d137e28 100644 --- a/protocol/contracts/dao/Setters.sol +++ b/protocol/contracts/dao/Setters.sol @@ -190,7 +190,7 @@ contract Setters is State, Getters { _state.epochs[epoch()].auction.minYield = yield; } } - setRelMaturity(uint256 couponEpochExpiry) internal { + function setRelMaturity(uint256 couponEpochExpiry) internal { if (couponEpochExpiry > _state.epochs[epoch()].auction.maxMaturity) { _state.epochs[epoch()].auction.maxMaturity = couponEpochExpiry; } else if (couponEpochExpiry < _state.epochs[epoch()].auction.minMaturity) { diff --git a/protocol/contracts/dao/State.sol b/protocol/contracts/dao/State.sol index 3189a9ab..04a46157 100644 --- a/protocol/contracts/dao/State.sol +++ b/protocol/contracts/dao/State.sol @@ -56,6 +56,7 @@ contract Epoch { uint256 couponMaturityEpoch; uint256 dollarAmount; uint256 couponAmount; + uint256 distance; bool selected; bool rejected; } From 5f7ab94bef3e7e5f83b675b25606e13b8c4e3cc5 Mon Sep 17 00:00:00 2001 From: cinquemb <1519017+cinquemb@users.noreply.github.com> Date: Sun, 3 Jan 2021 05:32:47 -0500 Subject: [PATCH 07/47] fix: sort on distance not on struct --- protocol/contracts/dao/Auction.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/protocol/contracts/dao/Auction.sol b/protocol/contracts/dao/Auction.sol index 7e9bba5d..b4bd2444 100644 --- a/protocol/contracts/dao/Auction.sol +++ b/protocol/contracts/dao/Auction.sol @@ -38,12 +38,12 @@ contract Auction is Setters { uint256 i = left; uint256 j = right; if(i==j) return; - uint256 pivot = arr[uint256(left + (right - left) / 2)]; + uint256 pivot = arr[uint256(left + (right - left) / 2)].distance; while (i <= j) { - while (arr[uint256(i)] < pivot) i++; - while (pivot < arr[uint256(j)]) j--; + while (arr[uint256(i)].distance < pivot) i++; + while (pivot < arr[uint256(j)].distance) j--; if (i <= j) { - (arr[uint256(i)], arr[uint256(j)]) = (arr[uint256(j)], arr[uint256(i)]); + (arr[uint256(i)]., arr[uint256(j)]) = (arr[uint256(j)], arr[uint256(i)]); i++; j--; } From 482e236d7d31815fb7d30efe99180337f72e7687 Mon Sep 17 00:00:00 2001 From: cinquemb <1519017+cinquemb@users.noreply.github.com> Date: Sun, 3 Jan 2021 08:21:36 -0500 Subject: [PATCH 08/47] add coupoun assignment/selected and rejected fix yeild calc --- protocol/contracts/dao/Auction.sol | 48 +++++++++++++++++------------- protocol/contracts/dao/Getters.sol | 8 +++++ protocol/contracts/dao/Market.sol | 24 ++++----------- protocol/contracts/dao/Setters.sol | 11 ++++++- protocol/contracts/dao/State.sol | 1 + 5 files changed, 51 insertions(+), 41 deletions(-) diff --git a/protocol/contracts/dao/Auction.sol b/protocol/contracts/dao/Auction.sol index b4bd2444..b12a7a77 100644 --- a/protocol/contracts/dao/Auction.sol +++ b/protocol/contracts/dao/Auction.sol @@ -18,16 +18,12 @@ pragma solidity ^0.5.17; pragma experimental ABIEncoderV2; import "@openzeppelin/contracts/math/SafeMath.sol"; -import "./Setters.sol"; -import "../external/Require.sol"; +import "./Comptroller.sol"; -contract Auction is Setters { +contract Auction is Comptroller { using SafeMath for uint256; - bytes32 private constant FILE = "Auction"; - event CouponAuctionCreated(); - event CouponAuctionCanceled(); - event CouponAuctionSettled(); + event AuctionCouponPurchase(address indexed account, uint256 indexed epoch, uint256 dollarAmount, uint256 couponAmount); function sortBidsByDistance(Epoch.CouponBidderState[] bids) public constant internal returns(Epoch.CouponBidderState[]) { quickSort(bids, uint256(0), uint256(bids.length - 1)); @@ -71,35 +67,32 @@ contract Auction is Setters { newAuction.minYield = 1000000000000000000000000; newAuction.maxYield = 0; setAuction(newAuction); - CouponAuctionCreated(newAuction); } function cancelCouponAuction() internal returns (bool success) { // can only cancel previous auction when in next epoch cancelAuction(epoch() - 1); - emit CouponAuctionCanceled(); return true; } - /* TODO: in the begining of the next epoch, all the available best bids that meet the specification will have their funds withdraw and coupons orders placed for the amount, premium, and maturity up to the amount of debt avaiable, the rest will have their funds sent back to them and */ function settleCouponAuction() internal returns (bool success) { - if (!isCouponAuctionFinished() && !isCouponAuctionCanceled){ + if (!isCouponAuctionFinished() && !isCouponAuctionCanceled()) { uint256 minMaturity = getMinMaturity(); uint256 maxMaturity = getMaxMaturity(); uint256 minYield = getMinYield(); uint256 maxYield = getMaxYield(); - Epoch.CouponBidderState[] memory bids; + Epoch.CouponBidderState[] memory bids; // loop over bids and compute distance - for(uint256 i = 0 ; i < getCouponAuctionBids(); i++) { + for (uint256 i = 0; i < getCouponAuctionBids(); i++) { uint256 couponMaturityEpoch = getCouponBidderState(getCouponBidderAtIndex(i)).couponMaturityEpoch; uint256 couponAmount = getCouponBidderState(getCouponBidderAtIndex(i)).couponAmount; uint256 dollarAmount = getCouponBidderState(getCouponBidderAtIndex(i)).dollarAmount; - uint256 yieldRel = dollarAmount.div( - couponAmount + uint256 yieldRel = couponAmount.div( + dollarAmount ).div( maxYield.sub(minYield) ); @@ -110,17 +103,30 @@ contract Auction is Setters { uint256 sumSquared = yieldRel.pow(2) + maturityRel.pow(2); uint256 distance = sqrt(sumSquared); getCouponBidderState(getCouponBidderAtIndex(i)).distance = distance; - bids.push(getCouponBidderState(getCouponBidderAtIndex(i)).distance); } // sort bids sortBidsByDistance(bids); - // assign coupons untill filled, reject the rest - - } - - return true; + // assign coupons until totalDebt filled, reject the rest + for (uint256 i = 0; i < bids.length; i++) { + if (totalDebt() >= bids[i].dollarAmount) { + if (!getCouponBidderStateRejected(bids[i].bidder) && !getCouponBidderStateRejected(bids[i].bidder)) { + // adds the maturity to the epoch call, subtracts off the base amount + uint256 epoch = epoch().add(bids[i].couponMaturityEpoch).sub(Constants.getCouponExpiration()); + burnFromAccount(bids[i].bidder, bids[i].dollarAmount); + incrementBalanceOfCoupons(bids[i].bidder, epoch, bids[i].couponAmount); + emit CouponPurchase(bids[i].bidder, epoch, dollarAmount, bids[i].couponAmount); + setCouponBidderStateSelected(bids[i].bidder); + } + } else { + setCouponBidderStateRejected(bids[i].bidder); + } + } + return true; + } else { + return true; + } } } \ No newline at end of file diff --git a/protocol/contracts/dao/Getters.sol b/protocol/contracts/dao/Getters.sol index 71a73f37..bb94fba5 100644 --- a/protocol/contracts/dao/Getters.sol +++ b/protocol/contracts/dao/Getters.sol @@ -199,6 +199,14 @@ contract Getters is State { return _state.epochs[epoch].auction._totalBids; } + function getCouponBidderStateSelected(address bidder) internal returns (bool) { + return _state.epochs[epoch()].auction.couponBidderState[bidder].selected; + } + + function getCouponBidderStateRejected(address bidder) internal returns (bool) { + return _state.epochs[epoch()].auction.couponBidderState[bidder].rejected; + } + /** * Governance */ diff --git a/protocol/contracts/dao/Market.sol b/protocol/contracts/dao/Market.sol index 00a11b71..67630458 100644 --- a/protocol/contracts/dao/Market.sol +++ b/protocol/contracts/dao/Market.sol @@ -72,7 +72,7 @@ contract Market is Comptroller, Curve { return calculateCouponPremium(dollar().totalSupply(), totalDebt(), amount); } - function purchaseCoupons(uint256 couponEpoch, uint256 dollarAmount) external returns (uint256) { + function purchaseCoupons(uint256 dollarAmount) external returns (uint256) { Require.that( dollarAmount > 0, FILE, @@ -85,17 +85,8 @@ contract Market is Comptroller, Curve { "Not enough debt" ); - uint256 epochMultiplier = couponEpoch.div(Constants.getCouponExpiration()); - uint256 defaultPremium = couponPremium(dollarAmount); - uint256 discountedPremium = defaultPremium.div( - uit256( - ln(e + epochMultiplier) - ).pow(2) - ); - uint256 actualEpoch = epochMultiplier.mul(Constants.getCouponExpiration()); - uint256 epoch = epoch().add(actualEpoch); - - uint256 couponAmount = dollarAmount.add(discountedPremium); + uint256 epoch = epoch(); + uint256 couponAmount = dollarAmount.add(couponPremium(dollarAmount)); burnFromAccount(msg.sender, dollarAmount); incrementBalanceOfCoupons(msg.sender, epoch, couponAmount); @@ -134,12 +125,7 @@ contract Market is Comptroller, Curve { emit CouponTransfer(sender, recipient, epoch, amount); } - function placeCouponAuctionBid(uint256 couponEpochExpiry, uint256 dollarAmount, uint256 maxCouponAmount) returns external (bool success) { - /* TODO: - Need to select best based on price (lowest yield / maxCouponAmount) - Need to select based on shortest maturity (1 epoch min) - */ - + function placeCouponAuctionBid(uint256 couponEpochExpiry, uint256 dollarAmount, uint256 maxCouponAmount) returns external (bool success) { // reject coupon amounts of 0 Require.that( maxCouponAmount > 0, @@ -154,7 +140,7 @@ contract Market is Comptroller, Curve { "Your price is too low" ); - setRelYield(dollarAmount.div(maxCouponAmount)); + setRelYield(maxCouponAmount.div(dollarAmount)); setRelMaturity(couponEpochExpiry); setCouponBidderState(msg.sender, couponEpochExpiry, dollarAmount, maxCouponAmount); setCouponBidderStateIndex(getCouponAuctionBidIndex(), msg.sender); diff --git a/protocol/contracts/dao/Setters.sol b/protocol/contracts/dao/Setters.sol index 1d137e28..948b08a4 100644 --- a/protocol/contracts/dao/Setters.sol +++ b/protocol/contracts/dao/Setters.sol @@ -170,9 +170,18 @@ contract Setters is State, Getters { function setCouponBidderState(address bidder, uint256 couponEpochExpiry, uint256 dollarAmount, uint256 maxCouponAmount) internal { CouponBidderState storage bidderState = _state.epochs[epoch()].auction.couponBidderState[bidder]; - bidderState.couponMaturityEpoch = couponEpochExpiry; // revisit this + bidderState.couponMaturityEpoch = couponEpochExpiry; bidderState.dollarAmount = dollarAmount; bidderState.couponAmount = maxCouponAmount; + bidderState.bidder = bidder; + } + + function setCouponBidderStateSelected(address bidder) internal { + _state.epochs[epoch()].auction.couponBidderState[bidder].selected = true; + } + + function setCouponBidderStateRejected(address bidder) internal { + _state.epochs[epoch()].auction.couponBidderState[bidder].rejected = true; } function setCouponBidderStateIndex(uint256 index, address bidder) internal { diff --git a/protocol/contracts/dao/State.sol b/protocol/contracts/dao/State.sol index 04a46157..2a588670 100644 --- a/protocol/contracts/dao/State.sol +++ b/protocol/contracts/dao/State.sol @@ -57,6 +57,7 @@ contract Epoch { uint256 dollarAmount; uint256 couponAmount; uint256 distance; + address bidder; bool selected; bool rejected; } From 7569fee8b19dd4fab8ab7e047021d39fd84d1306 Mon Sep 17 00:00:00 2001 From: cinquemb <1519017+cinquemb@users.noreply.github.com> Date: Sun, 3 Jan 2021 08:52:12 -0500 Subject: [PATCH 09/47] remove unused imports and constants --- protocol/contracts/dao/Market.sol | 7 ------- 1 file changed, 7 deletions(-) diff --git a/protocol/contracts/dao/Market.sol b/protocol/contracts/dao/Market.sol index 67630458..76fb9c57 100644 --- a/protocol/contracts/dao/Market.sol +++ b/protocol/contracts/dao/Market.sol @@ -18,8 +18,6 @@ pragma solidity ^0.5.17; pragma experimental ABIEncoderV2; import "@openzeppelin/contracts/math/SafeMath.sol"; -import "@abdk-libraries-solidity/ABDKMath64x64.sol"; -import "./Auction.sol"; import "./Curve.sol"; import "./Comptroller.sol"; import "../Constants.sol"; @@ -29,11 +27,6 @@ contract Market is Comptroller, Curve { bytes32 private constant FILE = "Market"; - /* Euler's number */ - uint256 private constant eN = 115792089237316195423570985008687907853269984664; - uint256 private constant eD = 42597529080697662913911602080600932014987715856; - uint256 private constant e = eN.div(eD); - event CouponExpiration(uint256 indexed epoch, uint256 couponsExpired, uint256 lessRedeemable, uint256 lessDebt, uint256 newBonded); event CouponPurchase(address indexed account, uint256 indexed epoch, uint256 dollarAmount, uint256 couponAmount); event CouponRedemption(address indexed account, uint256 indexed epoch, uint256 couponAmount); From ba31c111ff2b21887be4d24b0024b4c650435482 Mon Sep 17 00:00:00 2001 From: cinquemb <1519017+cinquemb@users.noreply.github.com> Date: Sun, 3 Jan 2021 09:00:10 -0500 Subject: [PATCH 10/47] dont subtract off constant epoch when assignment --- protocol/contracts/dao/Auction.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/protocol/contracts/dao/Auction.sol b/protocol/contracts/dao/Auction.sol index b12a7a77..2192c557 100644 --- a/protocol/contracts/dao/Auction.sol +++ b/protocol/contracts/dao/Auction.sol @@ -113,8 +113,7 @@ contract Auction is Comptroller { for (uint256 i = 0; i < bids.length; i++) { if (totalDebt() >= bids[i].dollarAmount) { if (!getCouponBidderStateRejected(bids[i].bidder) && !getCouponBidderStateRejected(bids[i].bidder)) { - // adds the maturity to the epoch call, subtracts off the base amount - uint256 epoch = epoch().add(bids[i].couponMaturityEpoch).sub(Constants.getCouponExpiration()); + uint256 epoch = epoch().add(bids[i].couponMaturityEpoch); burnFromAccount(bids[i].bidder, bids[i].dollarAmount); incrementBalanceOfCoupons(bids[i].bidder, epoch, bids[i].couponAmount); emit CouponPurchase(bids[i].bidder, epoch, dollarAmount, bids[i].couponAmount); From e53a3c8b976b164e8c1805971fac1bb5d89148fc Mon Sep 17 00:00:00 2001 From: cinquemb <1519017+cinquemb@users.noreply.github.com> Date: Sun, 3 Jan 2021 13:42:33 -0500 Subject: [PATCH 11/47] fixing some compiler errors --- protocol/contracts/dao/Auction.sol | 14 ++++++------ protocol/contracts/dao/Getters.sol | 34 +++++++++++++++++++++++++++--- protocol/contracts/dao/Market.sol | 4 ++-- protocol/contracts/dao/Setters.sol | 6 +++--- 4 files changed, 43 insertions(+), 15 deletions(-) diff --git a/protocol/contracts/dao/Auction.sol b/protocol/contracts/dao/Auction.sol index 2192c557..e3efdad0 100644 --- a/protocol/contracts/dao/Auction.sol +++ b/protocol/contracts/dao/Auction.sol @@ -18,14 +18,14 @@ pragma solidity ^0.5.17; pragma experimental ABIEncoderV2; import "@openzeppelin/contracts/math/SafeMath.sol"; -import "./Comptroller.sol"; +import "./Market.sol"; -contract Auction is Comptroller { +contract Auction is Market { using SafeMath for uint256; event AuctionCouponPurchase(address indexed account, uint256 indexed epoch, uint256 dollarAmount, uint256 couponAmount); - function sortBidsByDistance(Epoch.CouponBidderState[] bids) public constant internal returns(Epoch.CouponBidderState[]) { + function sortBidsByDistance(Epoch.CouponBidderState[] memory bids) internal returns(Epoch.CouponBidderState[] memory) { quickSort(bids, uint256(0), uint256(bids.length - 1)); return bids; } @@ -39,7 +39,7 @@ contract Auction is Comptroller { while (arr[uint256(i)].distance < pivot) i++; while (pivot < arr[uint256(j)].distance) j--; if (i <= j) { - (arr[uint256(i)]., arr[uint256(j)]) = (arr[uint256(j)], arr[uint256(i)]); + (arr[uint256(i)], arr[uint256(j)]) = (arr[uint256(j)], arr[uint256(i)]); i++; j--; } @@ -66,12 +66,12 @@ contract Auction is Comptroller { newAuction.maxMaturity = 0; newAuction.minYield = 1000000000000000000000000; newAuction.maxYield = 0; - setAuction(newAuction); + setCouponAuction(address(newAuction)); } function cancelCouponAuction() internal returns (bool success) { // can only cancel previous auction when in next epoch - cancelAuction(epoch() - 1); + cancelCounponAuction(epoch() - 1); return true; } @@ -116,7 +116,7 @@ contract Auction is Comptroller { uint256 epoch = epoch().add(bids[i].couponMaturityEpoch); burnFromAccount(bids[i].bidder, bids[i].dollarAmount); incrementBalanceOfCoupons(bids[i].bidder, epoch, bids[i].couponAmount); - emit CouponPurchase(bids[i].bidder, epoch, dollarAmount, bids[i].couponAmount); + emit CouponPurchase(bids[i].bidder, epoch, bids[i].dollarAmount, bids[i].couponAmount); setCouponBidderStateSelected(bids[i].bidder); } } else { diff --git a/protocol/contracts/dao/Getters.sol b/protocol/contracts/dao/Getters.sol index bb94fba5..bb8ba852 100644 --- a/protocol/contracts/dao/Getters.sol +++ b/protocol/contracts/dao/Getters.sol @@ -191,14 +191,18 @@ contract Getters is State { return epoch <= Constants.getBootstrappingPeriod(); } - function getCouponAuctionAtEpoch(uint256 epoch) internal returns (Auction){ - return _state.epochs[epoch].auction; + function getCouponAuctionAtEpoch(uint256 epoch) internal returns (address) { + return address(_state.epochs[epoch].auction); } - function getCouponAuctionBids() internal returns (uint256){ + function getCouponAuctionBids() internal returns (uint256) { return _state.epochs[epoch].auction._totalBids; } + function getCouponBidderState(address bidder) internal return (Epoch.CouponBidderState) { + return _state.epochs[epoch()].auction.couponBidderState[bidder]; + } + function getCouponBidderStateSelected(address bidder) internal returns (bool) { return _state.epochs[epoch()].auction.couponBidderState[bidder].selected; } @@ -207,6 +211,30 @@ contract Getters is State { return _state.epochs[epoch()].auction.couponBidderState[bidder].rejected; } + function isCouponAuctionFinished() internal returns (bool){ + return _state.epochs[epoch].auction.finished; + } + + function isCouponAuctionCanceled() internal returns (bool){ + return _state.epochs[epoch].auction.canceled; + } + + function getMinMaturity() internal returns (uint256) { + return _state.epochs[epoch()].auction.minMaturity; + } + + function getMaxMaturity() internal returns (uint256) { + return _state.epochs[epoch()].auction.maxMaturity; + } + + function getMinYield() internal returns (uint256) { + return _state.epochs[epoch()].auction.minYield; + } + + function getMaxYield() internal returns (uint256) { + return _state.epochs[epoch()].auction.maxYield; + } + /** * Governance */ diff --git a/protocol/contracts/dao/Market.sol b/protocol/contracts/dao/Market.sol index 76fb9c57..65f8c67f 100644 --- a/protocol/contracts/dao/Market.sol +++ b/protocol/contracts/dao/Market.sol @@ -118,7 +118,7 @@ contract Market is Comptroller, Curve { emit CouponTransfer(sender, recipient, epoch, amount); } - function placeCouponAuctionBid(uint256 couponEpochExpiry, uint256 dollarAmount, uint256 maxCouponAmount) returns external (bool success) { + function placeCouponAuctionBid(uint256 couponEpochExpiry, uint256 dollarAmount, uint256 maxCouponAmount) external returns (bool success) { // reject coupon amounts of 0 Require.that( maxCouponAmount > 0, @@ -136,7 +136,7 @@ contract Market is Comptroller, Curve { setRelYield(maxCouponAmount.div(dollarAmount)); setRelMaturity(couponEpochExpiry); setCouponBidderState(msg.sender, couponEpochExpiry, dollarAmount, maxCouponAmount); - setCouponBidderStateIndex(getCouponAuctionBidIndex(), msg.sender); + setCouponBidderStateIndex(getCouponAuctionBids(), msg.sender); incrementCouponAuctionBids(); return true; } diff --git a/protocol/contracts/dao/Setters.sol b/protocol/contracts/dao/Setters.sol index 948b08a4..799ddc89 100644 --- a/protocol/contracts/dao/Setters.sol +++ b/protocol/contracts/dao/Setters.sol @@ -149,9 +149,9 @@ contract Setters is State, Getters { _state.epochs[epoch].coupons.outstanding = 0; } - function setCouponAuction(Auction auction) internal { + function setCouponAuction(address auction) internal { if(!_state.epochs[epoch()].auction) { - _state.epochs[epoch()].auction = auction; + _state.epochs[epoch()].auction = Epoch.Auction(auction); } } @@ -168,7 +168,7 @@ contract Setters is State, Getters { } function setCouponBidderState(address bidder, uint256 couponEpochExpiry, uint256 dollarAmount, uint256 maxCouponAmount) internal { - CouponBidderState storage bidderState = _state.epochs[epoch()].auction.couponBidderState[bidder]; + Epoch.CouponBidderState storage bidderState = _state.epochs[epoch()].auction.couponBidderState[bidder]; bidderState.couponMaturityEpoch = couponEpochExpiry; bidderState.dollarAmount = dollarAmount; From 22a8e74b5aa501337899f27f9c4e5f18aad04b45 Mon Sep 17 00:00:00 2001 From: cinquemb <1519017+cinquemb@users.noreply.github.com> Date: Sun, 3 Jan 2021 14:12:08 -0500 Subject: [PATCH 12/47] fixing more compiler errors --- protocol/contracts/dao/Auction.sol | 27 +++++++++++---------------- protocol/contracts/dao/Getters.sol | 24 ++++++++++++++---------- protocol/contracts/dao/Market.sol | 4 ++-- protocol/contracts/dao/Setters.sol | 23 +++++++++++------------ 4 files changed, 38 insertions(+), 40 deletions(-) diff --git a/protocol/contracts/dao/Auction.sol b/protocol/contracts/dao/Auction.sol index e3efdad0..7c8d5d48 100644 --- a/protocol/contracts/dao/Auction.sol +++ b/protocol/contracts/dao/Auction.sol @@ -60,13 +60,8 @@ contract Auction is Market { } function createCouponAuction() internal { - Auction newAuction = new Auction(this); - newAuction._totalBids = 0; - newAuction.minMaturity = 1000000000000000000000000; - newAuction.maxMaturity = 0; - newAuction.minYield = 1000000000000000000000000; - newAuction.maxYield = 0; - setCouponAuction(address(newAuction)); + Auction newAuction = this; + setCouponAuction(newAuction); } function cancelCouponAuction() internal returns (bool success) { @@ -78,18 +73,18 @@ contract Auction is Market { function settleCouponAuction() internal returns (bool success) { if (!isCouponAuctionFinished() && !isCouponAuctionCanceled()) { - uint256 minMaturity = getMinMaturity(); - uint256 maxMaturity = getMaxMaturity(); - uint256 minYield = getMinYield(); - uint256 maxYield = getMaxYield(); + uint256 minMaturity = getCouponAuctionMinMaturity(); + uint256 maxMaturity = getCouponAuctionMaxMaturity(); + uint256 minYield = getCouponAuctionMinYield(); + uint256 maxYield = getCouponAuctionMaxYield(); Epoch.CouponBidderState[] memory bids; // loop over bids and compute distance for (uint256 i = 0; i < getCouponAuctionBids(); i++) { - uint256 couponMaturityEpoch = getCouponBidderState(getCouponBidderAtIndex(i)).couponMaturityEpoch; - uint256 couponAmount = getCouponBidderState(getCouponBidderAtIndex(i)).couponAmount; - uint256 dollarAmount = getCouponBidderState(getCouponBidderAtIndex(i)).dollarAmount; + uint256 couponMaturityEpoch = getCouponBidderState(getCouponBidderStateIndex(i)).couponMaturityEpoch; + uint256 couponAmount = getCouponBidderState(getCouponBidderStateIndex(i)).couponAmount; + uint256 dollarAmount = getCouponBidderState(getCouponBidderStateIndex(i)).dollarAmount; uint256 yieldRel = couponAmount.div( dollarAmount @@ -102,8 +97,8 @@ contract Auction is Market { uint256 sumSquared = yieldRel.pow(2) + maturityRel.pow(2); uint256 distance = sqrt(sumSquared); - getCouponBidderState(getCouponBidderAtIndex(i)).distance = distance; - bids.push(getCouponBidderState(getCouponBidderAtIndex(i)).distance); + getCouponBidderState(getCouponBidderStateIndex(i)).distance = distance; + bids.push(getCouponBidderState(getCouponBidderStateIndex(i)).distance); } // sort bids diff --git a/protocol/contracts/dao/Getters.sol b/protocol/contracts/dao/Getters.sol index bb8ba852..1befaf90 100644 --- a/protocol/contracts/dao/Getters.sol +++ b/protocol/contracts/dao/Getters.sol @@ -191,15 +191,15 @@ contract Getters is State { return epoch <= Constants.getBootstrappingPeriod(); } - function getCouponAuctionAtEpoch(uint256 epoch) internal returns (address) { - return address(_state.epochs[epoch].auction); + function getCouponAuctionAtEpoch(uint256 epoch) internal returns (Epoch.Auction storage) { + return _state.epochs[epoch].auction; } function getCouponAuctionBids() internal returns (uint256) { - return _state.epochs[epoch].auction._totalBids; + return _state.epochs[epoch()].auction._totalBids; } - function getCouponBidderState(address bidder) internal return (Epoch.CouponBidderState) { + function getCouponBidderState(address bidder) internal returns (Epoch.CouponBidderState storage) { return _state.epochs[epoch()].auction.couponBidderState[bidder]; } @@ -211,27 +211,31 @@ contract Getters is State { return _state.epochs[epoch()].auction.couponBidderState[bidder].rejected; } + function getCouponBidderStateIndex(uint256 index) internal returns (address) { + return _state.epochs[epoch()].auction.couponBidder[index]; + } + function isCouponAuctionFinished() internal returns (bool){ - return _state.epochs[epoch].auction.finished; + return _state.epochs[epoch()].auction.finished; } function isCouponAuctionCanceled() internal returns (bool){ - return _state.epochs[epoch].auction.canceled; + return _state.epochs[epoch()].auction.canceled; } - function getMinMaturity() internal returns (uint256) { + function getCouponAuctionMinMaturity() internal returns (uint256) { return _state.epochs[epoch()].auction.minMaturity; } - function getMaxMaturity() internal returns (uint256) { + function getCouponAuctionMaxMaturity() internal returns (uint256) { return _state.epochs[epoch()].auction.maxMaturity; } - function getMinYield() internal returns (uint256) { + function getCouponAuctionMinYield() internal returns (uint256) { return _state.epochs[epoch()].auction.minYield; } - function getMaxYield() internal returns (uint256) { + function getCouponAuctionMaxYield() internal returns (uint256) { return _state.epochs[epoch()].auction.maxYield; } diff --git a/protocol/contracts/dao/Market.sol b/protocol/contracts/dao/Market.sol index 65f8c67f..9fa981c5 100644 --- a/protocol/contracts/dao/Market.sol +++ b/protocol/contracts/dao/Market.sol @@ -133,8 +133,8 @@ contract Market is Comptroller, Curve { "Your price is too low" ); - setRelYield(maxCouponAmount.div(dollarAmount)); - setRelMaturity(couponEpochExpiry); + setCouponAuctionRelYield(maxCouponAmount.div(dollarAmount)); + setCouponAuctionRelMaturity(couponEpochExpiry); setCouponBidderState(msg.sender, couponEpochExpiry, dollarAmount, maxCouponAmount); setCouponBidderStateIndex(getCouponAuctionBids(), msg.sender); incrementCouponAuctionBids(); diff --git a/protocol/contracts/dao/Setters.sol b/protocol/contracts/dao/Setters.sol index 799ddc89..b3117d3d 100644 --- a/protocol/contracts/dao/Setters.sol +++ b/protocol/contracts/dao/Setters.sol @@ -149,22 +149,21 @@ contract Setters is State, Getters { _state.epochs[epoch].coupons.outstanding = 0; } - function setCouponAuction(address auction) internal { - if(!_state.epochs[epoch()].auction) { - _state.epochs[epoch()].auction = Epoch.Auction(auction); - } + function setCouponAuction(Epoch.Auction storage auction) internal { + _state.epochs[epoch()].auction = auction; + _state.epochs[epoch()].auction._totalBids = 0; + _state.epochs[epoch()].auction.minMaturity = 1000000000000000000000000; + _state.epochs[epoch()].auction.maxMaturity = 0; + _state.epochs[epoch()].auction.minYield = 1000000000000000000000000; + _state.epochs[epoch()].auction.maxYield = 0; } function cancelCounponAuction(uint256 epoch) internal { - if ((epoch() - 1) == epoch) { - _state.epochs[epoch].auction.canceled = true; - } + _state.epochs[epoch].auction.canceled = true; } function settleCouponAuction(uint256 epoch) internal { - if ((epoch() - 1) == epoch) { - _state.epochs[epoch].auction.finished = true; - } + _state.epochs[epoch].auction.finished = true; } function setCouponBidderState(address bidder, uint256 couponEpochExpiry, uint256 dollarAmount, uint256 maxCouponAmount) internal { @@ -192,14 +191,14 @@ contract Setters is State, Getters { _state.epochs[epoch()].auction._totalBids++; } - function setRelYield(uint256 yield) internal { + function setCouponAuctionRelYield(uint256 yield) internal { if (yield > _state.epochs[epoch()].auction.maxYield) { _state.epochs[epoch()].auction.maxYield = yield; } else if (yield < _state.epochs[epoch()].auction.minYield) { _state.epochs[epoch()].auction.minYield = yield; } } - function setRelMaturity(uint256 couponEpochExpiry) internal { + function setCouponAuctionRelMaturity(uint256 couponEpochExpiry) internal { if (couponEpochExpiry > _state.epochs[epoch()].auction.maxMaturity) { _state.epochs[epoch()].auction.maxMaturity = couponEpochExpiry; } else if (couponEpochExpiry < _state.epochs[epoch()].auction.minMaturity) { From 24f30f86f4d8b9cf376db99396a7a17962feae39 Mon Sep 17 00:00:00 2001 From: cinquemb <1519017+cinquemb@users.noreply.github.com> Date: Sun, 3 Jan 2021 22:44:40 -0500 Subject: [PATCH 13/47] first pass with implementing FPSBA --- protocol/contracts/dao/Auction.sol | 30 +++++++++++----------------- protocol/contracts/dao/Getters.sol | 24 +++++++++++----------- protocol/contracts/dao/Market.sol | 1 + protocol/contracts/dao/Regulator.sol | 24 +++++++++++++++++----- protocol/contracts/dao/Setters.sol | 8 ++++---- protocol/contracts/dao/State.sol | 5 +++-- 6 files changed, 51 insertions(+), 41 deletions(-) diff --git a/protocol/contracts/dao/Auction.sol b/protocol/contracts/dao/Auction.sol index 7c8d5d48..095dea51 100644 --- a/protocol/contracts/dao/Auction.sol +++ b/protocol/contracts/dao/Auction.sol @@ -19,9 +19,13 @@ pragma experimental ABIEncoderV2; import "@openzeppelin/contracts/math/SafeMath.sol"; import "./Market.sol"; +import "../external/Decimal.sol"; contract Auction is Market { using SafeMath for uint256; + using Decimal for Decimal.D256; + + Epoch.CouponBidderState[] private bids; event AuctionCouponPurchase(address indexed account, uint256 indexed epoch, uint256 dollarAmount, uint256 couponAmount); @@ -50,7 +54,7 @@ contract Auction is Market { quickSort(arr, i, right); } - function sqrt(uint256 x) internal returns (uint256 y) { + function sqrt(uint256 x) internal pure returns (uint256 y) { uint256 z = x.add(1).div(2); y = x; while (z < y) { @@ -59,26 +63,13 @@ contract Auction is Market { } } - function createCouponAuction() internal { - Auction newAuction = this; - setCouponAuction(newAuction); - } - - function cancelCouponAuction() internal returns (bool success) { - // can only cancel previous auction when in next epoch - cancelCounponAuction(epoch() - 1); - return true; - } - - function settleCouponAuction() internal returns (bool success) { + function settleCouponAuction() public returns (bool success) { if (!isCouponAuctionFinished() && !isCouponAuctionCanceled()) { uint256 minMaturity = getCouponAuctionMinMaturity(); uint256 maxMaturity = getCouponAuctionMaxMaturity(); uint256 minYield = getCouponAuctionMinYield(); - uint256 maxYield = getCouponAuctionMaxYield(); - - Epoch.CouponBidderState[] memory bids; + uint256 maxYield = getCouponAuctionMaxYield(); // loop over bids and compute distance for (uint256 i = 0; i < getCouponAuctionBids(); i++) { @@ -95,10 +86,13 @@ contract Auction is Market { maxMaturity.sub(minMaturity) ); - uint256 sumSquared = yieldRel.pow(2) + maturityRel.pow(2); + uint256 yieldRelSquared = Decimal.zero().add(yieldRel).pow(2).asUint256(); + uint256 maturityRelSquared = Decimal.zero().add(maturityRel).pow(2).asUint256(); + + uint256 sumSquared = yieldRelSquared.add(maturityRelSquared); uint256 distance = sqrt(sumSquared); getCouponBidderState(getCouponBidderStateIndex(i)).distance = distance; - bids.push(getCouponBidderState(getCouponBidderStateIndex(i)).distance); + bids.push(getCouponBidderState(getCouponBidderStateIndex(i))); } // sort bids diff --git a/protocol/contracts/dao/Getters.sol b/protocol/contracts/dao/Getters.sol index 1befaf90..78146f13 100644 --- a/protocol/contracts/dao/Getters.sol +++ b/protocol/contracts/dao/Getters.sol @@ -191,51 +191,51 @@ contract Getters is State { return epoch <= Constants.getBootstrappingPeriod(); } - function getCouponAuctionAtEpoch(uint256 epoch) internal returns (Epoch.Auction storage) { + function getCouponAuctionAtEpoch(uint256 epoch) internal view returns (Epoch.AuctionState storage) { return _state.epochs[epoch].auction; } - function getCouponAuctionBids() internal returns (uint256) { + function getCouponAuctionBids() internal view returns (uint256) { return _state.epochs[epoch()].auction._totalBids; } - function getCouponBidderState(address bidder) internal returns (Epoch.CouponBidderState storage) { + function getCouponBidderState(address bidder) internal view returns (Epoch.CouponBidderState storage) { return _state.epochs[epoch()].auction.couponBidderState[bidder]; } - function getCouponBidderStateSelected(address bidder) internal returns (bool) { + function getCouponBidderStateSelected(address bidder) internal view returns (bool) { return _state.epochs[epoch()].auction.couponBidderState[bidder].selected; } - function getCouponBidderStateRejected(address bidder) internal returns (bool) { + function getCouponBidderStateRejected(address bidder) internal view returns (bool) { return _state.epochs[epoch()].auction.couponBidderState[bidder].rejected; } - function getCouponBidderStateIndex(uint256 index) internal returns (address) { + function getCouponBidderStateIndex(uint256 index) internal view returns (address) { return _state.epochs[epoch()].auction.couponBidder[index]; } - function isCouponAuctionFinished() internal returns (bool){ + function isCouponAuctionFinished() internal view returns (bool){ return _state.epochs[epoch()].auction.finished; } - function isCouponAuctionCanceled() internal returns (bool){ + function isCouponAuctionCanceled() internal view returns (bool){ return _state.epochs[epoch()].auction.canceled; } - function getCouponAuctionMinMaturity() internal returns (uint256) { + function getCouponAuctionMinMaturity() internal view returns (uint256) { return _state.epochs[epoch()].auction.minMaturity; } - function getCouponAuctionMaxMaturity() internal returns (uint256) { + function getCouponAuctionMaxMaturity() internal view returns (uint256) { return _state.epochs[epoch()].auction.maxMaturity; } - function getCouponAuctionMinYield() internal returns (uint256) { + function getCouponAuctionMinYield() internal view returns (uint256) { return _state.epochs[epoch()].auction.minYield; } - function getCouponAuctionMaxYield() internal returns (uint256) { + function getCouponAuctionMaxYield() internal view returns (uint256) { return _state.epochs[epoch()].auction.maxYield; } diff --git a/protocol/contracts/dao/Market.sol b/protocol/contracts/dao/Market.sol index 9fa981c5..5ea71340 100644 --- a/protocol/contracts/dao/Market.sol +++ b/protocol/contracts/dao/Market.sol @@ -20,6 +20,7 @@ pragma experimental ABIEncoderV2; import "@openzeppelin/contracts/math/SafeMath.sol"; import "./Curve.sol"; import "./Comptroller.sol"; +import "./Auction.sol"; import "../Constants.sol"; contract Market is Comptroller, Curve { diff --git a/protocol/contracts/dao/Regulator.sol b/protocol/contracts/dao/Regulator.sol index 4d84f58a..7af1f94e 100644 --- a/protocol/contracts/dao/Regulator.sol +++ b/protocol/contracts/dao/Regulator.sol @@ -19,6 +19,7 @@ pragma experimental ABIEncoderV2; import "@openzeppelin/contracts/math/SafeMath.sol"; import "./Comptroller.sol"; +import "./Auction.sol"; import "../external/Decimal.sol"; import "../Constants.sol"; @@ -34,16 +35,29 @@ contract Regulator is Comptroller { Decimal.D256 memory price = oracleCapture(); if (price.greaterThan(Decimal.one())) { - /* TODO: Cancel any outstanding previous coupon auction */ - + Epoch.AuctionState storage auction = getCouponAuctionAtEpoch(epoch()); + + //check for outstanding auction, if exists cancel it + if (auction.couponAuction != address(0)){ + cancelCouponAuctionAtEpoch(epoch()); + } + growSupply(price); return; } if (price.lessThan(Decimal.one())) { - /* TODO: If any outstanding previous auction, fill acceptable bids for coupons, if none cancel previous auction */ - - /* TODO: Launch new auction for this epoch */ + Epoch.AuctionState storage auction = getCouponAuctionAtEpoch(epoch()); + + //check for outstanding auction, if exists settle it and start a new one + if (auction.couponAuction != address(0)){ + Auction currentCouponAuction = Auction(auction.couponAuction); + bool isAuctionSettled = currentCouponAuction.settleCouponAuction(); + finishCouponAuctionAtEpoch(epoch()); + } + Auction newCouponAuction = new Auction(); + initCouponAuction(address(newCouponAuction)); + shrinkSupply(price); return; } diff --git a/protocol/contracts/dao/Setters.sol b/protocol/contracts/dao/Setters.sol index b3117d3d..a9918829 100644 --- a/protocol/contracts/dao/Setters.sol +++ b/protocol/contracts/dao/Setters.sol @@ -149,8 +149,8 @@ contract Setters is State, Getters { _state.epochs[epoch].coupons.outstanding = 0; } - function setCouponAuction(Epoch.Auction storage auction) internal { - _state.epochs[epoch()].auction = auction; + function initCouponAuction(address auction) internal { + _state.epochs[epoch()].auction.couponAuction = auction; _state.epochs[epoch()].auction._totalBids = 0; _state.epochs[epoch()].auction.minMaturity = 1000000000000000000000000; _state.epochs[epoch()].auction.maxMaturity = 0; @@ -158,11 +158,11 @@ contract Setters is State, Getters { _state.epochs[epoch()].auction.maxYield = 0; } - function cancelCounponAuction(uint256 epoch) internal { + function cancelCouponAuctionAtEpoch(uint256 epoch) internal { _state.epochs[epoch].auction.canceled = true; } - function settleCouponAuction(uint256 epoch) internal { + function finishCouponAuctionAtEpoch(uint256 epoch) internal { _state.epochs[epoch].auction.finished = true; } diff --git a/protocol/contracts/dao/State.sol b/protocol/contracts/dao/State.sol index 2a588670..33286275 100644 --- a/protocol/contracts/dao/State.sol +++ b/protocol/contracts/dao/State.sol @@ -62,7 +62,7 @@ contract Epoch { bool rejected; } - struct Auction { + struct AuctionState { bool canceled; bool finished; uint256 minMaturity; @@ -72,12 +72,13 @@ contract Epoch { uint256 _totalBids; mapping(uint256 => address) couponBidder; mapping(address => CouponBidderState) couponBidderState; + address couponAuction; } struct State { uint256 bonded; Coupons coupons; - Auction auction; + AuctionState auction; } } From 5837ecc5ef277fade1a7c47aaec9d4dee9b65526 Mon Sep 17 00:00:00 2001 From: cinquemb <1519017+cinquemb@users.noreply.github.com> Date: Sun, 3 Jan 2021 22:50:40 -0500 Subject: [PATCH 14/47] changing contract inheritance of Auction --- protocol/contracts/dao/Auction.sol | 7 ++++--- protocol/contracts/dao/Market.sol | 1 - 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/protocol/contracts/dao/Auction.sol b/protocol/contracts/dao/Auction.sol index 095dea51..f949d1f3 100644 --- a/protocol/contracts/dao/Auction.sol +++ b/protocol/contracts/dao/Auction.sol @@ -18,10 +18,11 @@ pragma solidity ^0.5.17; pragma experimental ABIEncoderV2; import "@openzeppelin/contracts/math/SafeMath.sol"; -import "./Market.sol"; +import "./Comptroller.sol"; +import "../Constants.sol"; import "../external/Decimal.sol"; -contract Auction is Market { +contract Auction is Comptroller { using SafeMath for uint256; using Decimal for Decimal.D256; @@ -105,7 +106,7 @@ contract Auction is Market { uint256 epoch = epoch().add(bids[i].couponMaturityEpoch); burnFromAccount(bids[i].bidder, bids[i].dollarAmount); incrementBalanceOfCoupons(bids[i].bidder, epoch, bids[i].couponAmount); - emit CouponPurchase(bids[i].bidder, epoch, bids[i].dollarAmount, bids[i].couponAmount); + emit AuctionCouponPurchase(bids[i].bidder, epoch, bids[i].dollarAmount, bids[i].couponAmount); setCouponBidderStateSelected(bids[i].bidder); } } else { diff --git a/protocol/contracts/dao/Market.sol b/protocol/contracts/dao/Market.sol index 5ea71340..9fa981c5 100644 --- a/protocol/contracts/dao/Market.sol +++ b/protocol/contracts/dao/Market.sol @@ -20,7 +20,6 @@ pragma experimental ABIEncoderV2; import "@openzeppelin/contracts/math/SafeMath.sol"; import "./Curve.sol"; import "./Comptroller.sol"; -import "./Auction.sol"; import "../Constants.sol"; contract Market is Comptroller, Curve { From 3b58701bdd8941ae70f3ef84f33f60c61c1edb41 Mon Sep 17 00:00:00 2001 From: cinquemb <1519017+cinquemb@users.noreply.github.com> Date: Mon, 4 Jan 2021 07:47:48 -0500 Subject: [PATCH 15/47] starting to add mocks and tests --- protocol/contracts/mock/MockAuction.sol | 29 +++++++++++++++++++++++++ protocol/test/dao/Auction.tests.js | 11 ++++++++++ 2 files changed, 40 insertions(+) create mode 100644 protocol/contracts/mock/MockAuction.sol create mode 100644 protocol/test/dao/Auction.tests.js diff --git a/protocol/contracts/mock/MockAuction.sol b/protocol/contracts/mock/MockAuction.sol new file mode 100644 index 00000000..61449c33 --- /dev/null +++ b/protocol/contracts/mock/MockAuction.sol @@ -0,0 +1,29 @@ +/* + Copyright 2020 Empty Set Squad + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +pragma solidity ^0.5.17; +pragma experimental ABIEncoderV2; + +import "../dao/Auction.sol"; +import "./MockState.sol"; + +contract MockAuction is Auction { + constructor () public { } + + function settleCouponAuctionE() external returns (bool) { + return super.settleCouponAuction(); + } +} diff --git a/protocol/test/dao/Auction.tests.js b/protocol/test/dao/Auction.tests.js new file mode 100644 index 00000000..a2034dc4 --- /dev/null +++ b/protocol/test/dao/Auction.tests.js @@ -0,0 +1,11 @@ +const { accounts, contract } = require('@openzeppelin/test-environment'); + +const { BN, expectRevert, time } = require('@openzeppelin/test-helpers'); +const { expect } = require('chai'); + +const MockAuction = contract.fromArtifact('MockAuction'); + +describe('Auction', function () { + const [ ownerAddress ] = accounts; + +}); \ No newline at end of file From 1343eab48e4c571c5b5bd9a83412272e56beea1f Mon Sep 17 00:00:00 2001 From: cinquemb <1519017+cinquemb@users.noreply.github.com> Date: Mon, 4 Jan 2021 08:19:35 -0500 Subject: [PATCH 16/47] adding test conditions scaffoled for auction --- protocol/test/dao/Auction.tests.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/protocol/test/dao/Auction.tests.js b/protocol/test/dao/Auction.tests.js index a2034dc4..4726e589 100644 --- a/protocol/test/dao/Auction.tests.js +++ b/protocol/test/dao/Auction.tests.js @@ -8,4 +8,19 @@ const MockAuction = contract.fromArtifact('MockAuction'); describe('Auction', function () { const [ ownerAddress ] = accounts; + beforeEach(async function () { + this.auction = await MockAuction.new({from: ownerAddress}); + }); + + describe('when settling auction', function () { + describe('auction is not finished and not canceled', function () { + + }); + describe('auction is finished', function () { + + }); + describe('auction is canceled', function () { + + }); + }); }); \ No newline at end of file From ea81de4f61b1bf00da86a1955e9291adaf2e0908 Mon Sep 17 00:00:00 2001 From: cinquemb <1519017+cinquemb@users.noreply.github.com> Date: Mon, 4 Jan 2021 21:27:12 -0500 Subject: [PATCH 17/47] adding tests for placeCouponAuctionBid --- protocol/contracts/dao/Market.sol | 31 +++++++++---- protocol/test/dao/Market.test.js | 76 +++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 9 deletions(-) diff --git a/protocol/contracts/dao/Market.sol b/protocol/contracts/dao/Market.sol index 9fa981c5..eab6f861 100644 --- a/protocol/contracts/dao/Market.sol +++ b/protocol/contracts/dao/Market.sol @@ -32,6 +32,7 @@ contract Market is Comptroller, Curve { event CouponRedemption(address indexed account, uint256 indexed epoch, uint256 couponAmount); event CouponTransfer(address indexed from, address indexed to, uint256 indexed epoch, uint256 value); event CouponApproval(address indexed owner, address indexed spender, uint256 value); + event CouponBidPlaced(address indexed account, uint256 indexed epoch, uint256 dollarAmount, uint256 maxCouponAmount); function step() internal { // Expire prior coupons @@ -118,26 +119,38 @@ contract Market is Comptroller, Curve { emit CouponTransfer(sender, recipient, epoch, amount); } - function placeCouponAuctionBid(uint256 couponEpochExpiry, uint256 dollarAmount, uint256 maxCouponAmount) external returns (bool success) { - // reject coupon amounts of 0 + function placeCouponAuctionBid(uint256 couponEpochExpiry, uint256 dollarAmount, uint256 maxCouponAmount) external returns (bool) { Require.that( - maxCouponAmount > 0, + couponEpochExpiry > 0, FILE, - "Your coupon amount is too low" + "Must have non-zero expiry" ); - - // reject payments of 0 + Require.that( dollarAmount > 0, FILE, - "Your price is too low" + "Must bid non-zero amount" + ); + + Require.that( + maxCouponAmount > 0, + FILE, + "Must bid on non-zero amount" + ); + + Require.that( + totalDebt() >= dollarAmount, + FILE, + "Not enough debt" ); + uint256 epoch = epoch().add(couponEpochExpiry); setCouponAuctionRelYield(maxCouponAmount.div(dollarAmount)); - setCouponAuctionRelMaturity(couponEpochExpiry); - setCouponBidderState(msg.sender, couponEpochExpiry, dollarAmount, maxCouponAmount); + setCouponAuctionRelMaturity(epoch); + setCouponBidderState(msg.sender, epoch, dollarAmount, maxCouponAmount); setCouponBidderStateIndex(getCouponAuctionBids(), msg.sender); incrementCouponAuctionBids(); + emit CouponBidPlaced(msg.sender, epoch, dollarAmount, maxCouponAmount); return true; } } diff --git a/protocol/test/dao/Market.test.js b/protocol/test/dao/Market.test.js index 2d23b59f..47692cb7 100644 --- a/protocol/test/dao/Market.test.js +++ b/protocol/test/dao/Market.test.js @@ -142,6 +142,82 @@ describe('Market', function () { }); }); + describe('placeCouponAuctionBid', function () { + describe('before call', function () { + beforeEach(async function () { + await this.market.incrementTotalDebtE(100000); + }); + }); + + describe('zero expiry', function () { + it('reverts', async function () { + await expectRevert(this.market.placeCouponAuctionBid(0, 100, 500, {from: userAddress}), "Market: Must have non-zero expiry"); + }); + }); + + describe('no dollar amount', function () { + it('reverts', async function () { + await expectRevert(this.market.placeCouponAuctionBid(1, 0, 500, {from: userAddress}), "Market: Must bid non-zero amount"); + }); + }); + + describe('no coupon amount', function () { + it('reverts', async function () { + await expectRevert(this.market.placeCouponAuctionBid(1, 1, 0, {from: userAddress}), "Market: Must bid on non-zero amount"); + }); + }); + + describe('no debt', function () { + it('total net is correct', async function () { + expect(await this.market.totalNet()).to.be.bignumber.equal(new BN(1000000)); + }); + + it('reverts', async function () { + await expectRevert(this.market.placeCouponAuctionBid(1, 100000, 100000000), "Market: Not enough debt"); + }); + }); + + describe('on single call', function () { + beforeEach(async function () { + await this.market.incrementTotalDebtE(100000); + this.result = await this.market.placeCouponAuctionBid(1, 100, 500, {from: userAddress}); + this.txHash = this.result.tx; + }); + + it('emits CouponBidPlaced event', async function () { + const event = await expectEvent.inTransaction(this.txHash, MockMarket, 'CouponBidPlaced', { + account: userAddress, + }); + + expect(event.args.epoch).to.be.bignumber.equal(new BN(2)); + expect(event.args.dollarAmount).to.be.bignumber.equal(new BN(100)); + expect(event.args.couponAmount).to.be.bignumber.equal(new BN(500))); + }); + }); + + describe('multiple calls', function () { + beforeEach(async function () { + await this.market.incrementTotalDebtE(1000000); + await this.market.placeCouponAuctionBid(1, 1000, 50000, {from: userAddress}); + await this.market.placeCouponAuctionBid(1, 2000, 50000, {from: userAddress}); + this.result = await this.market.placeCouponAuctionBid(2, 1000, 50000, {from: userAddress}); + this.txHash = this.result.tx; + }); + + + it('emits CouponBidPlaced event', async function () { + const event = await expectEvent.inTransaction(this.txHash, MockMarket, 'CouponPurchase', { + account: userAddress, + }); + + expect(event.args.epoch).to.be.bignumber.equal(new BN(3)); + expect(event.args.dollarAmount).to.be.bignumber.equal(new BN(2000)); + expect(event.args.couponAmount).to.be.bignumber.equal(new BN(50000))); + }); + }); + + }); + describe('redeemCoupons', function () { beforeEach(async function () { await this.market.incrementTotalDebtE(100000); From 827157b5ca07c15c8a85ecadcf65c4de1e554fde Mon Sep 17 00:00:00 2001 From: cinquemb <1519017+cinquemb@users.noreply.github.com> Date: Tue, 5 Jan 2021 04:11:20 -0500 Subject: [PATCH 18/47] updating state mocks, incorperating dollar amount in euclidian distance calcs, starting adding tests for new state/setter/getter functions --- protocol/contracts/dao/Auction.sol | 11 +++++-- protocol/contracts/dao/Getters.sol | 8 +++++ protocol/contracts/dao/Market.sol | 1 + protocol/contracts/dao/Setters.sol | 11 +++++++ protocol/contracts/dao/State.sol | 2 ++ protocol/contracts/mock/MockState.sol | 43 +++++++++++++++++++++++++ protocol/test/dao/State.test.js | 45 +++++++++++++++++++++++++++ 7 files changed, 119 insertions(+), 2 deletions(-) diff --git a/protocol/contracts/dao/Auction.sol b/protocol/contracts/dao/Auction.sol index f949d1f3..734f14d2 100644 --- a/protocol/contracts/dao/Auction.sol +++ b/protocol/contracts/dao/Auction.sol @@ -70,7 +70,9 @@ contract Auction is Comptroller { uint256 minMaturity = getCouponAuctionMinMaturity(); uint256 maxMaturity = getCouponAuctionMaxMaturity(); uint256 minYield = getCouponAuctionMinYield(); - uint256 maxYield = getCouponAuctionMaxYield(); + uint256 maxYield = getCouponAuctionMaxYield(); + uint256 minDollarAmount = getCouponAuctionMinDollarAmount(); + uint256 maxDollarAmount = getCouponAuctionMinDollarAmount(); // loop over bids and compute distance for (uint256 i = 0; i < getCouponAuctionBids(); i++) { @@ -86,11 +88,16 @@ contract Auction is Comptroller { uint256 maturityRel = couponMaturityEpoch.div( maxMaturity.sub(minMaturity) ); + uint256 dollarRelMax = dollarAmount.div( + maxDollarAmount.sub(minDollarAmount) + ); + uint256 dollarRel = Decimal.one().sub(dollarRelMax).asUint256(); uint256 yieldRelSquared = Decimal.zero().add(yieldRel).pow(2).asUint256(); uint256 maturityRelSquared = Decimal.zero().add(maturityRel).pow(2).asUint256(); + uint256 dollarRelSquared = Decimal.zero().add(dollarRel).pow(2).asUint256(); - uint256 sumSquared = yieldRelSquared.add(maturityRelSquared); + uint256 sumSquared = yieldRelSquared.add(maturityRelSquared).add(dollarRelSquared); uint256 distance = sqrt(sumSquared); getCouponBidderState(getCouponBidderStateIndex(i)).distance = distance; bids.push(getCouponBidderState(getCouponBidderStateIndex(i))); diff --git a/protocol/contracts/dao/Getters.sol b/protocol/contracts/dao/Getters.sol index 78146f13..ddbeb97b 100644 --- a/protocol/contracts/dao/Getters.sol +++ b/protocol/contracts/dao/Getters.sol @@ -239,6 +239,14 @@ contract Getters is State { return _state.epochs[epoch()].auction.maxYield; } + function getCouponAuctionMinDollarAmount() internal view returns (uint256) { + return _state.epochs[epoch()].auction.minDollarAmount; + } + + function getCouponAuctionMaxDollarAmount() internal view returns (uint256) { + return _state.epochs[epoch()].auction.maxDollarAmount; + } + /** * Governance */ diff --git a/protocol/contracts/dao/Market.sol b/protocol/contracts/dao/Market.sol index eab6f861..200ee030 100644 --- a/protocol/contracts/dao/Market.sol +++ b/protocol/contracts/dao/Market.sol @@ -146,6 +146,7 @@ contract Market is Comptroller, Curve { uint256 epoch = epoch().add(couponEpochExpiry); setCouponAuctionRelYield(maxCouponAmount.div(dollarAmount)); + setCouponAuctionRelDollarAmount(dollarAmount); setCouponAuctionRelMaturity(epoch); setCouponBidderState(msg.sender, epoch, dollarAmount, maxCouponAmount); setCouponBidderStateIndex(getCouponAuctionBids(), msg.sender); diff --git a/protocol/contracts/dao/Setters.sol b/protocol/contracts/dao/Setters.sol index a9918829..0331323d 100644 --- a/protocol/contracts/dao/Setters.sol +++ b/protocol/contracts/dao/Setters.sol @@ -156,6 +156,8 @@ contract Setters is State, Getters { _state.epochs[epoch()].auction.maxMaturity = 0; _state.epochs[epoch()].auction.minYield = 1000000000000000000000000; _state.epochs[epoch()].auction.maxYield = 0; + _state.epochs[epoch()].auction.minDollarAmount = 1000000000000000000000000; + _state.epochs[epoch()].auction.maxDollarAmount = 0; } function cancelCouponAuctionAtEpoch(uint256 epoch) internal { @@ -198,6 +200,7 @@ contract Setters is State, Getters { _state.epochs[epoch()].auction.minYield = yield; } } + function setCouponAuctionRelMaturity(uint256 couponEpochExpiry) internal { if (couponEpochExpiry > _state.epochs[epoch()].auction.maxMaturity) { _state.epochs[epoch()].auction.maxMaturity = couponEpochExpiry; @@ -205,6 +208,14 @@ contract Setters is State, Getters { _state.epochs[epoch()].auction.minMaturity = couponEpochExpiry; } } + + function setCouponAuctionRelDollarAmount(uint256 couponDollarAmount) internal { + if (couponDollarAmount > _state.epochs[epoch()].auction.maxDollarAmount) { + _state.epochs[epoch()].auction.maxDollarAmount = couponDollarAmount; + } else if (couponDollarAmount < _state.epochs[epoch()].auction.minDollarAmount) { + _state.epochs[epoch()].auction.minDollarAmount = couponDollarAmount; + } + } /** diff --git a/protocol/contracts/dao/State.sol b/protocol/contracts/dao/State.sol index 33286275..2aa22220 100644 --- a/protocol/contracts/dao/State.sol +++ b/protocol/contracts/dao/State.sol @@ -69,6 +69,8 @@ contract Epoch { uint256 maxMaturity; uint256 minYield; uint256 maxYield; + uint256 minDollarAmount; + uint256 maxDollarAmount; uint256 _totalBids; mapping(uint256 => address) couponBidder; mapping(address => CouponBidderState) couponBidderState; diff --git a/protocol/contracts/mock/MockState.sol b/protocol/contracts/mock/MockState.sol index c745a3d0..0ffd0195 100644 --- a/protocol/contracts/mock/MockState.sol +++ b/protocol/contracts/mock/MockState.sol @@ -119,6 +119,49 @@ contract MockState is Setters { super.eliminateOutstandingCoupons(epoch); } + function initCouponAuctionE(address auction) external { + super.initCouponAuction(auction); + } + + function cancelCouponAuctionAtEpochE(uint256 epoch) external { + super.cancelCouponAuctionAtEpoch(epoch); + } + + function finishCouponAuctionAtEpochE(uint256 epoch) external { + super.finishCouponAuctionAtEpoch(epoch); + } + + function setCouponBidderStateE(address bidder, uint256 couponEpochExpiry, uint256 dollarAmount, uint256 maxCouponAmount) external { + super.setCouponBidderState(bidder, couponEpochExpiry, dollarAmount, maxCouponAmount); + } + + function setCouponBidderStateSelectedE(address bidder) external { + super.setCouponBidderStateSelected(bidder); + } + + function setCouponBidderStateRejectedE(address bidder) external { + super.setCouponBidderStateRejected(bidder); + } + + function setCouponBidderStateIndexE(uint256 index, address bidder) external { + super.setCouponBidderStateIndex(index, bidder); + } + + function incrementCouponAuctionBidsE() external { + super.incrementCouponAuctionBids(); + } + + function setCouponAuctionRelYieldE(uint256 yield) external { + super.setCouponAuctionRelYield(yield); + } + function setCouponAuctionRelMaturityE(uint256 couponEpochExpiry) external { + super.setCouponAuctionRelMaturity(couponEpochExpiry); + } + + function setCouponAuctionRelDollarAmountE(uint256 couponDollarAmount) external { + super.setCouponAuctionRelDollarAmount(couponDollarAmount); + } + /** * Governance */ diff --git a/protocol/test/dao/State.test.js b/protocol/test/dao/State.test.js index 1be80c53..44b0789b 100644 --- a/protocol/test/dao/State.test.js +++ b/protocol/test/dao/State.test.js @@ -750,6 +750,51 @@ describe('State', function () { }); }); + + describe('initCouponAuction', function () { + describe('when called', function () { + beforeEach('call', async function () { + // need to create mock auction object and pass into this + await this.setters.initCouponAuctionE(1); + }); + + it('has auction set', async function () { + // need to check that auction address exists, non zero and has all the other states initializd properly + expect(await this.setters.getCouponAuctionAtEpoch(1)).to.be.bignumber.equal(new BN(91)); + }); + }); + }); + + describe('cancelCouponAuctionAtEpoch', function () { + }); + + describe('finishCouponAuctionAtEpoch', function () { + }); + + describe('setCouponBidderState', function () { + }); + + describe('setCouponBidderStateSelected', function () { + }); + + describe('setCouponBidderStateRejected', function () { + }); + + describe('setCouponBidderStateIndex', function () { + }); + + describe('incrementCouponAuctionBids', function () { + }); + + describe('setCouponAuctionRelYield', function () { + }); + + describe('setCouponAuctionRelMaturity', function () { + }); + + describe('setCouponAuctionRelDollarAmount', function () { + }); + /** * Governance */ From 71612847bba7f243c1d1f6e97a3a50a7bd6f8d7f Mon Sep 17 00:00:00 2001 From: cinquemb <1519017+cinquemb@users.noreply.github.com> Date: Tue, 5 Jan 2021 18:19:40 -0500 Subject: [PATCH 19/47] adding some auction internals --- protocol/contracts/dao/Auction.sol | 64 ++++++++++++++++++++++----- protocol/contracts/dao/Getters.sol | 40 +++++++++++++++-- protocol/contracts/dao/Market.sol | 2 +- protocol/contracts/dao/Setters.sol | 48 ++++++++++++++++---- protocol/contracts/dao/State.sol | 14 ++++-- protocol/contracts/mock/MockState.sol | 36 ++++++++++++++- protocol/test/dao/State.test.js | 26 ++++++++++- 7 files changed, 200 insertions(+), 30 deletions(-) diff --git a/protocol/contracts/dao/Auction.sol b/protocol/contracts/dao/Auction.sol index 734f14d2..6a4d6461 100644 --- a/protocol/contracts/dao/Auction.sol +++ b/protocol/contracts/dao/Auction.sol @@ -28,7 +28,13 @@ contract Auction is Comptroller { Epoch.CouponBidderState[] private bids; - event AuctionCouponPurchase(address indexed account, uint256 indexed epoch, uint256 dollarAmount, uint256 couponAmount); + uint256 private totalFilled = 0; + uint256 private minExpiryFilled = 10000000000000000000; + uint256 private maxExpiryFilled = 0; + uint256 private sumExpiryFilled = 0; + uint256 private minYieldFilled = 10000000000000000000; + uint256 private maxYieldFilled = 0; + uint256 private sumYieldFilled = 0; function sortBidsByDistance(Epoch.CouponBidderState[] memory bids) internal returns(Epoch.CouponBidderState[] memory) { quickSort(bids, uint256(0), uint256(bids.length - 1)); @@ -67,8 +73,8 @@ contract Auction is Comptroller { function settleCouponAuction() public returns (bool success) { if (!isCouponAuctionFinished() && !isCouponAuctionCanceled()) { - uint256 minMaturity = getCouponAuctionMinMaturity(); - uint256 maxMaturity = getCouponAuctionMaxMaturity(); + uint256 minExpiry = getCouponAuctionMinExpiry(); + uint256 maxExpiry = getCouponAuctionMaxExpiry(); uint256 minYield = getCouponAuctionMinYield(); uint256 maxYield = getCouponAuctionMaxYield(); uint256 minDollarAmount = getCouponAuctionMinDollarAmount(); @@ -76,7 +82,7 @@ contract Auction is Comptroller { // loop over bids and compute distance for (uint256 i = 0; i < getCouponAuctionBids(); i++) { - uint256 couponMaturityEpoch = getCouponBidderState(getCouponBidderStateIndex(i)).couponMaturityEpoch; + uint256 couponExpiryEpoch = getCouponBidderState(getCouponBidderStateIndex(i)).couponExpiryEpoch; uint256 couponAmount = getCouponBidderState(getCouponBidderStateIndex(i)).couponAmount; uint256 dollarAmount = getCouponBidderState(getCouponBidderStateIndex(i)).dollarAmount; @@ -85,8 +91,8 @@ contract Auction is Comptroller { ).div( maxYield.sub(minYield) ); - uint256 maturityRel = couponMaturityEpoch.div( - maxMaturity.sub(minMaturity) + uint256 ExpiryRel = couponExpiryEpoch.div( + maxExpiry.sub(minExpiry) ); uint256 dollarRelMax = dollarAmount.div( maxDollarAmount.sub(minDollarAmount) @@ -94,10 +100,10 @@ contract Auction is Comptroller { uint256 dollarRel = Decimal.one().sub(dollarRelMax).asUint256(); uint256 yieldRelSquared = Decimal.zero().add(yieldRel).pow(2).asUint256(); - uint256 maturityRelSquared = Decimal.zero().add(maturityRel).pow(2).asUint256(); + uint256 ExpiryRelSquared = Decimal.zero().add(ExpiryRel).pow(2).asUint256(); uint256 dollarRelSquared = Decimal.zero().add(dollarRel).pow(2).asUint256(); - uint256 sumSquared = yieldRelSquared.add(maturityRelSquared).add(dollarRelSquared); + uint256 sumSquared = yieldRelSquared.add(ExpiryRelSquared).add(dollarRelSquared); uint256 distance = sqrt(sumSquared); getCouponBidderState(getCouponBidderStateIndex(i)).distance = distance; bids.push(getCouponBidderState(getCouponBidderStateIndex(i))); @@ -106,23 +112,59 @@ contract Auction is Comptroller { // sort bids sortBidsByDistance(bids); + + // assign coupons until totalDebt filled, reject the rest for (uint256 i = 0; i < bids.length; i++) { if (totalDebt() >= bids[i].dollarAmount) { if (!getCouponBidderStateRejected(bids[i].bidder) && !getCouponBidderStateRejected(bids[i].bidder)) { - uint256 epoch = epoch().add(bids[i].couponMaturityEpoch); + uint256 yield = bids[i].couponAmount.div( + bids[i].dollarAmount + ); + + if (yield < minYieldFilled) { + minYieldFilled = yield; + } else if (yield > maxYieldFilled) { + maxYieldFilled = yield; + } + + if (bids[i].couponExpiryEpoch < minExpiryFilled) { + minExpiryFilled = bids[i].couponExpiryEpoch; + } else if (bids[i].couponExpiryEpoch > maxExpiryFilled) { + maxExpiryFilled = bids[i].couponExpiryEpoch; + } + + sumYieldFilled.add(yield); + sumExpiryFilled.add(bids[i].couponExpiryEpoch); + + uint256 epoch = epoch().add(bids[i].couponExpiryEpoch); burnFromAccount(bids[i].bidder, bids[i].dollarAmount); incrementBalanceOfCoupons(bids[i].bidder, epoch, bids[i].couponAmount); - emit AuctionCouponPurchase(bids[i].bidder, epoch, bids[i].dollarAmount, bids[i].couponAmount); setCouponBidderStateSelected(bids[i].bidder); + totalFilled++; } } else { setCouponBidderStateRejected(bids[i].bidder); } } + + // set auction internals + uint256 avgYieldFilled = sumYieldFilled.div(totalFilled); + uint256 avgExpiryFilled = sumExpiryFilled.div(totalFilled); + uint256 bidToCover = bids.length.div(totalFilled); + + setMinExpiryFilled(minExpiryFilled); + setMaxExpiryFilled(maxExpiryFilled); + setAvgExpiryFilled(avgExpiryFilled); + setMinYieldFilled(minYieldFilled); + setMaxYieldFilled(maxYieldFilled); + setAvgYieldFilled(avgYieldFilled); + setBidToCover(bidToCover); + setTotalFilled(totalFilled); + return true; } else { - return true; + return false; } } } \ No newline at end of file diff --git a/protocol/contracts/dao/Getters.sol b/protocol/contracts/dao/Getters.sol index ddbeb97b..f9e75c4e 100644 --- a/protocol/contracts/dao/Getters.sol +++ b/protocol/contracts/dao/Getters.sol @@ -223,12 +223,12 @@ contract Getters is State { return _state.epochs[epoch()].auction.canceled; } - function getCouponAuctionMinMaturity() internal view returns (uint256) { - return _state.epochs[epoch()].auction.minMaturity; + function getCouponAuctionMinExpiry() internal view returns (uint256) { + return _state.epochs[epoch()].auction.minExpiry; } - function getCouponAuctionMaxMaturity() internal view returns (uint256) { - return _state.epochs[epoch()].auction.maxMaturity; + function getCouponAuctionMaxExpiry() internal view returns (uint256) { + return _state.epochs[epoch()].auction.maxExpiry; } function getCouponAuctionMinYield() internal view returns (uint256) { @@ -247,6 +247,38 @@ contract Getters is State { return _state.epochs[epoch()].auction.maxDollarAmount; } + function getMinExpiryFilled(uint256 epoch) public view returns (uint256) { + return _state.epochs[epoch].auction.minExpiryFilled; + } + + function getMaxExpiryFilled(uint256 epoch) public view returns (uint256) { + return _state.epochs[epoch].auction.maxExpiryFilled; + } + + function getAvgExpiryFilled(uint256 epoch) public view returns (uint256) { + return _state.epochs[epoch].auction.avgExpiryFilled; + } + + function getMinYieldFilled(uint256 epoch) public view returns (uint256) { + return _state.epochs[epoch].auction.minYieldFilled; + } + + function getMaxYieldFilled(uint256 epoch) public view returns (uint256) { + return _state.epochs[epoch].auction.maxYieldFilled; + } + + function getAvgYieldFilled(uint256 epoch) public view returns (uint256) { + return _state.epochs[epoch].auction.avgYieldFilled; + } + + function getBidToCover(uint256 epoch) public view returns (uint256) { + return _state.epochs[epoch].auction.bidToCover; + } + + function getTotalFilled(uint256 epoch) public view returns (uint256) { + return _state.epochs[epoch].auction.totalFilled; + } + /** * Governance */ diff --git a/protocol/contracts/dao/Market.sol b/protocol/contracts/dao/Market.sol index 200ee030..00615ca2 100644 --- a/protocol/contracts/dao/Market.sol +++ b/protocol/contracts/dao/Market.sol @@ -147,7 +147,7 @@ contract Market is Comptroller, Curve { uint256 epoch = epoch().add(couponEpochExpiry); setCouponAuctionRelYield(maxCouponAmount.div(dollarAmount)); setCouponAuctionRelDollarAmount(dollarAmount); - setCouponAuctionRelMaturity(epoch); + setCouponAuctionRelExpiry(epoch); setCouponBidderState(msg.sender, epoch, dollarAmount, maxCouponAmount); setCouponBidderStateIndex(getCouponAuctionBids(), msg.sender); incrementCouponAuctionBids(); diff --git a/protocol/contracts/dao/Setters.sol b/protocol/contracts/dao/Setters.sol index 0331323d..3e3fa719 100644 --- a/protocol/contracts/dao/Setters.sol +++ b/protocol/contracts/dao/Setters.sol @@ -152,8 +152,8 @@ contract Setters is State, Getters { function initCouponAuction(address auction) internal { _state.epochs[epoch()].auction.couponAuction = auction; _state.epochs[epoch()].auction._totalBids = 0; - _state.epochs[epoch()].auction.minMaturity = 1000000000000000000000000; - _state.epochs[epoch()].auction.maxMaturity = 0; + _state.epochs[epoch()].auction.minExpiry = 1000000000000000000000000; + _state.epochs[epoch()].auction.maxExpiry = 0; _state.epochs[epoch()].auction.minYield = 1000000000000000000000000; _state.epochs[epoch()].auction.maxYield = 0; _state.epochs[epoch()].auction.minDollarAmount = 1000000000000000000000000; @@ -171,7 +171,7 @@ contract Setters is State, Getters { function setCouponBidderState(address bidder, uint256 couponEpochExpiry, uint256 dollarAmount, uint256 maxCouponAmount) internal { Epoch.CouponBidderState storage bidderState = _state.epochs[epoch()].auction.couponBidderState[bidder]; - bidderState.couponMaturityEpoch = couponEpochExpiry; + bidderState.couponExpiryEpoch = couponEpochExpiry; bidderState.dollarAmount = dollarAmount; bidderState.couponAmount = maxCouponAmount; bidderState.bidder = bidder; @@ -201,11 +201,11 @@ contract Setters is State, Getters { } } - function setCouponAuctionRelMaturity(uint256 couponEpochExpiry) internal { - if (couponEpochExpiry > _state.epochs[epoch()].auction.maxMaturity) { - _state.epochs[epoch()].auction.maxMaturity = couponEpochExpiry; - } else if (couponEpochExpiry < _state.epochs[epoch()].auction.minMaturity) { - _state.epochs[epoch()].auction.minMaturity = couponEpochExpiry; + function setCouponAuctionRelExpiry(uint256 couponEpochExpiry) internal { + if (couponEpochExpiry > _state.epochs[epoch()].auction.maxExpiry) { + _state.epochs[epoch()].auction.maxExpiry = couponEpochExpiry; + } else if (couponEpochExpiry < _state.epochs[epoch()].auction.minExpiry) { + _state.epochs[epoch()].auction.minExpiry = couponEpochExpiry; } } @@ -216,6 +216,38 @@ contract Setters is State, Getters { _state.epochs[epoch()].auction.minDollarAmount = couponDollarAmount; } } + + function setMinExpiryFilled(uint256 minExpiryFilled) internal { + _state.epochs[epoch()].auction.minExpiryFilled = minExpiryFilled; + } + + function setMaxExpiryFilled(uint256 maxExpiryFilled) internal { + _state.epochs[epoch()].auction.maxExpiryFilled = maxExpiryFilled; + } + + function setAvgExpiryFilled(uint256 avgExpiryFilled) internal { + _state.epochs[epoch()].auction.avgExpiryFilled = avgExpiryFilled; + } + + function setMinYieldFilled(uint256 minYieldFilled) internal { + _state.epochs[epoch()].auction.minYieldFilled = minYieldFilled; + } + + function setMaxYieldFilled(uint256 maxYieldFilled) internal { + _state.epochs[epoch()].auction.maxYieldFilled = maxYieldFilled; + } + + function setAvgYieldFilled(uint256 avgYieldFilled) internal { + _state.epochs[epoch()].auction.avgYieldFilled = avgYieldFilled; + } + + function setBidToCover(uint256 bidToCover) internal { + _state.epochs[epoch()].auction.bidToCover = bidToCover; + } + + function setTotalFilled(uint256 totalFilled) internal { + _state.epochs[epoch()].auction.totalFilled = totalFilled; + } /** diff --git a/protocol/contracts/dao/State.sol b/protocol/contracts/dao/State.sol index 2aa22220..537949b7 100644 --- a/protocol/contracts/dao/State.sol +++ b/protocol/contracts/dao/State.sol @@ -53,7 +53,7 @@ contract Epoch { } struct CouponBidderState { - uint256 couponMaturityEpoch; + uint256 couponExpiryEpoch; uint256 dollarAmount; uint256 couponAmount; uint256 distance; @@ -65,13 +65,21 @@ contract Epoch { struct AuctionState { bool canceled; bool finished; - uint256 minMaturity; - uint256 maxMaturity; + uint256 minExpiry; + uint256 maxExpiry; + uint256 minExpiryFilled; + uint256 maxExpiryFilled; + uint256 avgExpiryFilled; uint256 minYield; uint256 maxYield; + uint256 minYieldFilled; + uint256 maxYieldFilled; + uint256 avgYieldFilled; uint256 minDollarAmount; uint256 maxDollarAmount; uint256 _totalBids; + uint256 totalFilled; + uint256 bidToCover; mapping(uint256 => address) couponBidder; mapping(address => CouponBidderState) couponBidderState; address couponAuction; diff --git a/protocol/contracts/mock/MockState.sol b/protocol/contracts/mock/MockState.sol index 0ffd0195..b5becb40 100644 --- a/protocol/contracts/mock/MockState.sol +++ b/protocol/contracts/mock/MockState.sol @@ -154,14 +154,46 @@ contract MockState is Setters { function setCouponAuctionRelYieldE(uint256 yield) external { super.setCouponAuctionRelYield(yield); } - function setCouponAuctionRelMaturityE(uint256 couponEpochExpiry) external { - super.setCouponAuctionRelMaturity(couponEpochExpiry); + function setCouponAuctionRelExpiryE(uint256 couponEpochExpiry) external { + super.setCouponAuctionRelExpiry(couponEpochExpiry); } function setCouponAuctionRelDollarAmountE(uint256 couponDollarAmount) external { super.setCouponAuctionRelDollarAmount(couponDollarAmount); } + function setMinExpiryFilledE(uint256 minExpiryFilled) external { + super.setMinExpiryFilled(minExpiryFilled); + } + + function setMaxExpiryFilledE(uint256 maxExpiryFilled) external { + super.setMaxExpiryFilled(maxExpiryFilled); + } + + function setAvgExpiryFilledE(uint256 avgExpiryFilled) external { + super.setAvgExpiryFilled(avgExpiryFilled); + } + + function setMinYieldFilledE(uint256 minYieldFilled) external { + super.setMinYieldFilled(minYieldFilled); + } + + function setMaxYieldFilledE(uint256 maxYieldFilled) external { + super.setMaxYieldFilled(maxYieldFilled); + } + + function setAvgYieldFilledE(uint256 avgYieldFilled) external { + super.setAvgYieldFilled(avgYieldFilled); + } + + function setBidToCoverE(uint256 bidToCover) external { + super.setBidToCover(bidToCover); + } + + function setTotalFilledE(uint256 totalFilled) external { + super.setTotalFilled(totalFilled); + } + /** * Governance */ diff --git a/protocol/test/dao/State.test.js b/protocol/test/dao/State.test.js index 44b0789b..eb01bea9 100644 --- a/protocol/test/dao/State.test.js +++ b/protocol/test/dao/State.test.js @@ -789,12 +789,36 @@ describe('State', function () { describe('setCouponAuctionRelYield', function () { }); - describe('setCouponAuctionRelMaturity', function () { + describe('setCouponAuctionRelExpiry', function () { }); describe('setCouponAuctionRelDollarAmount', function () { }); + describe('setMinExpiryFilled', function () { + }); + + describe('setMaxExpiryFilled', function () { + }); + + describe('setAvgExpiryFilled', function () { + }); + + describe('setMinYieldFilled', function () { + }); + + describe('setMaxYieldFilled', function () { + }); + + describe('setAvgYieldFilled', function () { + }); + + describe('setBidToCover', function () { + }); + + describe('setTotalFilled', function () { + }); + /** * Governance */ From f42edee164d3a2984504c725d56bb9197414928b Mon Sep 17 00:00:00 2001 From: cinquemb <1519017+cinquemb@users.noreply.github.com> Date: Tue, 5 Jan 2021 18:40:09 -0500 Subject: [PATCH 20/47] tight packing auction structs --- protocol/contracts/dao/State.sol | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/protocol/contracts/dao/State.sol b/protocol/contracts/dao/State.sol index 537949b7..9dadba43 100644 --- a/protocol/contracts/dao/State.sol +++ b/protocol/contracts/dao/State.sol @@ -53,13 +53,13 @@ contract Epoch { } struct CouponBidderState { - uint256 couponExpiryEpoch; - uint256 dollarAmount; - uint256 couponAmount; - uint256 distance; - address bidder; bool selected; bool rejected; + address bidder; + uint256 distance; + uint256 dollarAmount; + uint256 couponAmount; + uint256 couponExpiryEpoch; } struct AuctionState { @@ -67,22 +67,22 @@ contract Epoch { bool finished; uint256 minExpiry; uint256 maxExpiry; - uint256 minExpiryFilled; - uint256 maxExpiryFilled; - uint256 avgExpiryFilled; uint256 minYield; uint256 maxYield; + uint256 _totalBids; + uint256 totalFilled; + uint256 bidToCover; + address couponAuction; uint256 minYieldFilled; uint256 maxYieldFilled; uint256 avgYieldFilled; + uint256 minExpiryFilled; + uint256 maxExpiryFilled; + uint256 avgExpiryFilled; uint256 minDollarAmount; uint256 maxDollarAmount; - uint256 _totalBids; - uint256 totalFilled; - uint256 bidToCover; mapping(uint256 => address) couponBidder; mapping(address => CouponBidderState) couponBidderState; - address couponAuction; } struct State { From c9c6bc4bf2c41dd91ededb63c41f75866fa58381 Mon Sep 17 00:00:00 2001 From: cinquemb <1519017+cinquemb@users.noreply.github.com> Date: Tue, 5 Jan 2021 19:17:51 -0500 Subject: [PATCH 21/47] adding modifier so that only dao can settle auction --- protocol/contracts/dao/Auction.sol | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/protocol/contracts/dao/Auction.sol b/protocol/contracts/dao/Auction.sol index 6a4d6461..4f7b6df0 100644 --- a/protocol/contracts/dao/Auction.sol +++ b/protocol/contracts/dao/Auction.sol @@ -26,6 +26,8 @@ contract Auction is Comptroller { using SafeMath for uint256; using Decimal for Decimal.D256; + bytes32 private constant FILE = "Auction"; + Epoch.CouponBidderState[] private bids; uint256 private totalFilled = 0; @@ -70,7 +72,7 @@ contract Auction is Comptroller { } } - function settleCouponAuction() public returns (bool success) { + function settleCouponAuction() public onlyDao returns (bool success) { if (!isCouponAuctionFinished() && !isCouponAuctionCanceled()) { uint256 minExpiry = getCouponAuctionMinExpiry(); @@ -167,4 +169,14 @@ contract Auction is Comptroller { return false; } } + + modifier onlyDao() { + Require.that( + msg.sender == Constants.getDaoAddress(), + FILE, + "Not dao" + ); + + _; + } } \ No newline at end of file From 3afd5f41450f1ae2825b0ed79e68738b1a43cef4 Mon Sep 17 00:00:00 2001 From: cinquemb <1519017+cinquemb@users.noreply.github.com> Date: Tue, 5 Jan 2021 21:35:57 -0500 Subject: [PATCH 22/47] adding new state tests --- protocol/test/dao/Market.test.js | 4 +- protocol/test/dao/State.test.js | 207 ++++++++++++++++++++++++++++++- 2 files changed, 204 insertions(+), 7 deletions(-) diff --git a/protocol/test/dao/Market.test.js b/protocol/test/dao/Market.test.js index 47692cb7..07bf37ea 100644 --- a/protocol/test/dao/Market.test.js +++ b/protocol/test/dao/Market.test.js @@ -191,7 +191,7 @@ describe('Market', function () { expect(event.args.epoch).to.be.bignumber.equal(new BN(2)); expect(event.args.dollarAmount).to.be.bignumber.equal(new BN(100)); - expect(event.args.couponAmount).to.be.bignumber.equal(new BN(500))); + expect(event.args.couponAmount).to.be.bignumber.equal(new BN(500)); }); }); @@ -212,7 +212,7 @@ describe('Market', function () { expect(event.args.epoch).to.be.bignumber.equal(new BN(3)); expect(event.args.dollarAmount).to.be.bignumber.equal(new BN(2000)); - expect(event.args.couponAmount).to.be.bignumber.equal(new BN(50000))); + expect(event.args.couponAmount).to.be.bignumber.equal(new BN(50000)); }); }); diff --git a/protocol/test/dao/State.test.js b/protocol/test/dao/State.test.js index eb01bea9..e76b1cef 100644 --- a/protocol/test/dao/State.test.js +++ b/protocol/test/dao/State.test.js @@ -4,6 +4,7 @@ const { BN, expectRevert, time } = require('@openzeppelin/test-helpers'); const { expect } = require('chai'); const MockState = contract.fromArtifact('MockState'); +const MockAuction = contract.fromArtifact('MockAuction'); const BOOTSTRAPPING_END_TIMESTAMP = 1600905600; const EPOCH_START = 1602288000; @@ -14,6 +15,7 @@ describe('State', function () { beforeEach(async function () { this.setters = await MockState.new({from: ownerAddress}); + this.auction = await MockAuction.new({from: ownerAddress}); }); /** @@ -754,69 +756,264 @@ describe('State', function () { describe('initCouponAuction', function () { describe('when called', function () { beforeEach('call', async function () { - // need to create mock auction object and pass into this - await this.setters.initCouponAuctionE(1); + await this.setters.initCouponAuctionE(this.auction); }); it('has auction set', async function () { - // need to check that auction address exists, non zero and has all the other states initializd properly - expect(await this.setters.getCouponAuctionAtEpoch(1)).to.be.bignumber.equal(new BN(91)); + expect(await this.setters.getCouponAuctionAtEpoch(1)).to.be.equal(this.auction); }); }); }); describe('cancelCouponAuctionAtEpoch', function () { + describe('when called', function () { + beforeEach('call', async function () { + await this.setters.initCouponAuctionE(this.auction); + await this.setters.cancelCouponAuctionAtEpochE(1); + }); + + it('has auction cancled', async function () { + expect(await this.setters.getCouponAuctionAtEpoch(1)).canceled.to.be.equal(true); + }); + }); }); describe('finishCouponAuctionAtEpoch', function () { + describe('when called', function () { + beforeEach('call', async function () { + await this.setters.initCouponAuctionE(this.auction); + await this.setters.finishCouponAuctionAtEpochE(1); + }); + + it('has auction finished', async function () { + expect(await this.setters.getCouponAuctionAtEpoch(1)).finished.to.be.equal(true); + }); + }); }); describe('setCouponBidderState', function () { + describe('when called', function () { + beforeEach('call', async function () { + await this.setters.initCouponAuctionE(this.auction); + await this.setters.setCouponBidderStateE(userAddress, 1, 100, 500); + }); + + it('has bidder state set', async function () { + expect(await this.setters.getCouponBidderState(userAddress)).couponExpiryEpoch.to.be.equal(new BN(1)); + expect(await this.setters.getCouponBidderState(userAddress)).dollarAmount.to.be.equal(new BN(100)); + expect(await this.setters.getCouponBidderState(userAddress)).couponAmount.to.be.equal(new BN(500)); + expect(await this.setters.getCouponBidderState(userAddress)).couponAmount.to.be.equal(userAddress); + }); + }); }); describe('setCouponBidderStateSelected', function () { + describe('when called', function () { + beforeEach('call', async function () { + await this.setters.initCouponAuctionE(this.auction); + await this.setters.setCouponBidderStateE(userAddress, 1, 100, 500); + await this.setters.setCouponBidderStateSelectedE(userAddress); + }); + + it('has bidder bid selected', async function () { + expect(await this.setters.getCouponBidderState(userAddress)).selected.to.be.equal(true); + }); + }); }); describe('setCouponBidderStateRejected', function () { - }); + describe('when called', function () { + beforeEach('call', async function () { + await this.setters.initCouponAuctionE(this.auction); + await this.setters.setCouponBidderStateE(userAddress, 1, 100, 500); + await this.setters.setCouponBidderStateRejectedE(userAddress); + }); + it('has bidder bid rejected', async function () { + expect(await this.setters.getCouponBidderState(userAddress)).rejected.to.be.equal(true); + }); + }); + }); describe('setCouponBidderStateIndex', function () { + describe('when called', function () { + beforeEach('call', async function () { + await this.setters.initCouponAuctionE(this.auction); + await this.setters.setCouponBidderStateE(userAddress, 1, 100, 500); + await this.setters.setCouponBidderStateIndexE(1, userAddress); + }); + + it('has bidder state index at specific index set to bidder', async function () { + expect(await this.setters.getCouponBidderStateIndexE(1)).to.be.equal(userAddress); + }); + }); }); describe('incrementCouponAuctionBids', function () { + describe('when called', function () { + beforeEach('call', async function () { + await this.setters.initCouponAuctionE(this.auction); + await this.setters.incrementCouponAuctionBidsE(); + }); + + it('has bidder state index at specific index set to bidder', async function () { + expect(await this.setters.getCouponAuctionBids()).to.be.equal(new BN(1)); + }); + }); }); describe('setCouponAuctionRelYield', function () { + describe('when called', function () { + beforeEach('call', async function () { + await this.setters.initCouponAuctionE(this.auction); + await this.setters.setCouponAuctionRelYieldE(10); + await this.setters.setCouponAuctionRelYieldE(100); + }); + + it('has min yield set to 10', async function () { + expect(await this.setters.getCouponAuctionMinYield()).to.be.equal(new BN(10)); + }); + it('has max yield set to 100', async function () { + expect(await this.setters.getCouponAuctionMaxYield()).to.be.equal(new BN(100)); + }); + }); }); describe('setCouponAuctionRelExpiry', function () { + describe('when called', function () { + beforeEach('call', async function () { + await this.setters.initCouponAuctionE(this.auction); + await this.setters.setCouponAuctionRelExpiryE(10); + await this.setters.setCouponAuctionRelExpiryE(100); + }); + + it('has min expiry set to 10', async function () { + expect(await this.setters.getCouponAuctionMinExpiry()).to.be.equal(new BN(10)); + }); + it('has max expiry set to 100', async function () { + expect(await this.setters.getCouponAuctionMaxExpiry()).to.be.equal(new BN(100)); + }); + }); }); describe('setCouponAuctionRelDollarAmount', function () { + describe('when called', function () { + beforeEach('call', async function () { + await this.setters.initCouponAuctionE(this.auction); + await this.setters.setCouponAuctionRelDollarAmountE(10); + await this.setters.setCouponAuctionRelDollarAmountE(100); + }); + + it('has min dollar amount set to 10', async function () { + expect(await this.setters.getCouponAuctionMinDollarAmount()).to.be.equal(new BN(10)); + }); + it('has max dollar amount set to 100', async function () { + expect(await this.setters.getCouponAuctionMaxDollarAmount()).to.be.equal(new BN(100)); + }); + }); }); describe('setMinExpiryFilled', function () { + describe('when called', function () { + beforeEach('call', async function () { + await this.setters.initCouponAuctionE(this.auction); + await this.setters.setMinExpiryFilledE(10); + }); + + it('has min expiry filled set to 10', async function () { + expect(await this.setters.getCouponAuctionAtEpoch(1)).minExpiryFilled.to.be.equal(new BN(10)); + }); + }); }); describe('setMaxExpiryFilled', function () { + describe('when called', function () { + beforeEach('call', async function () { + await this.setters.initCouponAuctionE(this.auction); + await this.setters.setMaxExpiryFilledE(10); + }); + + it('has max expiry filled set to 10', async function () { + expect(await this.setters.getCouponAuctionAtEpoch(1)).maxExpiryFilled.to.be.equal(new BN(10)); + }); + }); }); describe('setAvgExpiryFilled', function () { + describe('when called', function () { + beforeEach('call', async function () { + await this.setters.initCouponAuctionE(this.auction); + await this.setters.setAvgExpiryFilled(10); + }); + + it('has avg expiry filled set to 10', async function () { + expect(await this.setters.getCouponAuctionAtEpoch(1)).avgExpiryFilled.to.be.equal(new BN(10)); + }); + }); }); describe('setMinYieldFilled', function () { + describe('when called', function () { + beforeEach('call', async function () { + await this.setters.initCouponAuctionE(this.auction); + await this.setters.setMinYieldFilledE(10); + }); + + it('has min yield filled set to 10', async function () { + expect(await this.setters.getCouponAuctionAtEpoch(1)).minYieldFilled.to.be.equal(new BN(10)); + }); + }); }); describe('setMaxYieldFilled', function () { + describe('when called', function () { + beforeEach('call', async function () { + await this.setters.initCouponAuctionE(this.auction); + await this.setters.setMaxYieldFilledE(10); + }); + + it('has max yield filled set to 10', async function () { + expect(await this.setters.getCouponAuctionAtEpoch(1)).maxYieldFilled.to.be.equal(new BN(10)); + }); + }); }); describe('setAvgYieldFilled', function () { + describe('when called', function () { + beforeEach('call', async function () { + await this.setters.initCouponAuctionE(this.auction); + await this.setters.setAvgYieldFilledE(10); + }); + + it('has avg yield filled set to 10', async function () { + expect(await this.setters.getCouponAuctionAtEpoch(1)).avgYieldFilled.to.be.equal(new BN(10)); + }); + }); }); describe('setBidToCover', function () { + describe('when called', function () { + beforeEach('call', async function () { + await this.setters.initCouponAuctionE(this.auction); + await this.setters.setBidToCoverE(10); + }); + + it('has bid to cover set to 10', async function () { + expect(await this.setters.getBidToCover(1)).bidToCover.to.be.equal(new BN(10)); + }); + }); }); describe('setTotalFilled', function () { + describe('when called', function () { + beforeEach('call', async function () { + await this.setters.initCouponAuctionE(this.auction); + await this.setters.setTotalFilledE(100); + }); + + it('has total bids filled to 100', async function () { + expect(await this.setters.getTotalFilled(1)).totalFilled.to.be.equal(new BN(100)); + }); + }); }); /** From f16f5d48166daa32b2b9cb23205473454a6041c9 Mon Sep 17 00:00:00 2001 From: cinquemb <1519017+cinquemb@users.noreply.github.com> Date: Wed, 6 Jan 2021 04:36:06 -0500 Subject: [PATCH 23/47] fixing market test --- protocol/test/dao/Market.test.js | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/protocol/test/dao/Market.test.js b/protocol/test/dao/Market.test.js index 07bf37ea..db1aee2e 100644 --- a/protocol/test/dao/Market.test.js +++ b/protocol/test/dao/Market.test.js @@ -168,10 +168,6 @@ describe('Market', function () { }); describe('no debt', function () { - it('total net is correct', async function () { - expect(await this.market.totalNet()).to.be.bignumber.equal(new BN(1000000)); - }); - it('reverts', async function () { await expectRevert(this.market.placeCouponAuctionBid(1, 100000, 100000000), "Market: Not enough debt"); }); @@ -181,17 +177,15 @@ describe('Market', function () { beforeEach(async function () { await this.market.incrementTotalDebtE(100000); this.result = await this.market.placeCouponAuctionBid(1, 100, 500, {from: userAddress}); - this.txHash = this.result.tx; }); it('emits CouponBidPlaced event', async function () { - const event = await expectEvent.inTransaction(this.txHash, MockMarket, 'CouponBidPlaced', { + const event = await expectEvent(this.result, 'CouponBidPlaced', { account: userAddress, }); - expect(event.args.epoch).to.be.bignumber.equal(new BN(2)); expect(event.args.dollarAmount).to.be.bignumber.equal(new BN(100)); - expect(event.args.couponAmount).to.be.bignumber.equal(new BN(500)); + expect(event.args.maxCouponAmount).to.be.bignumber.equal(new BN(500)); }); }); @@ -201,18 +195,17 @@ describe('Market', function () { await this.market.placeCouponAuctionBid(1, 1000, 50000, {from: userAddress}); await this.market.placeCouponAuctionBid(1, 2000, 50000, {from: userAddress}); this.result = await this.market.placeCouponAuctionBid(2, 1000, 50000, {from: userAddress}); - this.txHash = this.result.tx; }); it('emits CouponBidPlaced event', async function () { - const event = await expectEvent.inTransaction(this.txHash, MockMarket, 'CouponPurchase', { + const event = await expectEvent(this.result, 'CouponBidPlaced', { account: userAddress, }); expect(event.args.epoch).to.be.bignumber.equal(new BN(3)); - expect(event.args.dollarAmount).to.be.bignumber.equal(new BN(2000)); - expect(event.args.couponAmount).to.be.bignumber.equal(new BN(50000)); + expect(event.args.dollarAmount).to.be.bignumber.equal(new BN(1000)); + expect(event.args.maxCouponAmount).to.be.bignumber.equal(new BN(50000)); }); }); From ae3ce9bc099e35898cda34a496fbcb722db4826e Mon Sep 17 00:00:00 2001 From: cinquemb <1519017+cinquemb@users.noreply.github.com> Date: Wed, 6 Jan 2021 19:12:39 -0500 Subject: [PATCH 24/47] fixing stuff with market tests, working on auction tests --- protocol/contracts/dao/Auction.sol | 58 +++--- protocol/contracts/dao/Regulator.sol | 5 +- protocol/contracts/mock/MockAuction.sol | 38 +++- protocol/contracts/mock/MockState.sol | 74 ------- protocol/test/dao/Auction.test.js | 138 +++++++++++++ protocol/test/dao/Auction.tests.js | 26 --- protocol/test/dao/State.test.js | 264 ------------------------ 7 files changed, 199 insertions(+), 404 deletions(-) create mode 100644 protocol/test/dao/Auction.test.js delete mode 100644 protocol/test/dao/Auction.tests.js diff --git a/protocol/contracts/dao/Auction.sol b/protocol/contracts/dao/Auction.sol index 4f7b6df0..1809ed88 100644 --- a/protocol/contracts/dao/Auction.sol +++ b/protocol/contracts/dao/Auction.sol @@ -72,40 +72,38 @@ contract Auction is Comptroller { } } - function settleCouponAuction() public onlyDao returns (bool success) { + function settleCouponAuction() internal returns (bool success) { if (!isCouponAuctionFinished() && !isCouponAuctionCanceled()) { - - uint256 minExpiry = getCouponAuctionMinExpiry(); - uint256 maxExpiry = getCouponAuctionMaxExpiry(); - uint256 minYield = getCouponAuctionMinYield(); - uint256 maxYield = getCouponAuctionMaxYield(); - uint256 minDollarAmount = getCouponAuctionMinDollarAmount(); - uint256 maxDollarAmount = getCouponAuctionMinDollarAmount(); + uint256 yieldRelNorm = getCouponAuctionMaxYield() - getCouponAuctionMinYield(); + uint256 expiryRelNorm = getCouponAuctionMaxExpiry() - getCouponAuctionMinExpiry(); + uint256 dollarRelNorm = getCouponAuctionMaxDollarAmount() - getCouponAuctionMinDollarAmount(); // loop over bids and compute distance for (uint256 i = 0; i < getCouponAuctionBids(); i++) { - uint256 couponExpiryEpoch = getCouponBidderState(getCouponBidderStateIndex(i)).couponExpiryEpoch; - uint256 couponAmount = getCouponBidderState(getCouponBidderStateIndex(i)).couponAmount; - uint256 dollarAmount = getCouponBidderState(getCouponBidderStateIndex(i)).dollarAmount; - - uint256 yieldRel = couponAmount.div( - dollarAmount - ).div( - maxYield.sub(minYield) + Decimal.D256 memory yieldRel = Decimal.ratio( + Decimal.ratio( + getCouponBidderState(getCouponBidderStateIndex(i)).couponAmount, + getCouponBidderState(getCouponBidderStateIndex(i)).dollarAmount + ).asUint256(), + yieldRelNorm ); - uint256 ExpiryRel = couponExpiryEpoch.div( - maxExpiry.sub(minExpiry) + + Decimal.D256 memory expiryRel = Decimal.ratio( + getCouponBidderState(getCouponBidderStateIndex(i)).couponExpiryEpoch, + expiryRelNorm ); - uint256 dollarRelMax = dollarAmount.div( - maxDollarAmount.sub(minDollarAmount) + + Decimal.D256 memory dollarRelMax = Decimal.ratio( + getCouponBidderState(getCouponBidderStateIndex(i)).dollarAmount, + dollarRelNorm ); - uint256 dollarRel = Decimal.one().sub(dollarRelMax).asUint256(); + uint256 dollarRel = 1 - dollarRelMax.asUint256(); - uint256 yieldRelSquared = Decimal.zero().add(yieldRel).pow(2).asUint256(); - uint256 ExpiryRelSquared = Decimal.zero().add(ExpiryRel).pow(2).asUint256(); - uint256 dollarRelSquared = Decimal.zero().add(dollarRel).pow(2).asUint256(); + Decimal.D256 memory yieldRelSquared = yieldRel.pow(2); + Decimal.D256 memory expiryRelSquared = expiryRel.pow(2); + Decimal.D256 memory dollarRelSquared = Decimal.D256(dollarRel).pow(2); - uint256 sumSquared = yieldRelSquared.add(ExpiryRelSquared).add(dollarRelSquared); + uint256 sumSquared = yieldRelSquared.add(expiryRelSquared).add(dollarRelSquared).asUint256(); uint256 distance = sqrt(sumSquared); getCouponBidderState(getCouponBidderStateIndex(i)).distance = distance; bids.push(getCouponBidderState(getCouponBidderStateIndex(i))); @@ -169,14 +167,4 @@ contract Auction is Comptroller { return false; } } - - modifier onlyDao() { - Require.that( - msg.sender == Constants.getDaoAddress(), - FILE, - "Not dao" - ); - - _; - } } \ No newline at end of file diff --git a/protocol/contracts/dao/Regulator.sol b/protocol/contracts/dao/Regulator.sol index 7af1f94e..c0df9220 100644 --- a/protocol/contracts/dao/Regulator.sol +++ b/protocol/contracts/dao/Regulator.sol @@ -23,7 +23,7 @@ import "./Auction.sol"; import "../external/Decimal.sol"; import "../Constants.sol"; -contract Regulator is Comptroller { +contract Regulator is Comptroller, Auction { using SafeMath for uint256; using Decimal for Decimal.D256; @@ -51,8 +51,7 @@ contract Regulator is Comptroller { //check for outstanding auction, if exists settle it and start a new one if (auction.couponAuction != address(0)){ - Auction currentCouponAuction = Auction(auction.couponAuction); - bool isAuctionSettled = currentCouponAuction.settleCouponAuction(); + bool isAuctionSettled = settleCouponAuction(); finishCouponAuctionAtEpoch(epoch()); } Auction newCouponAuction = new Auction(); diff --git a/protocol/contracts/mock/MockAuction.sol b/protocol/contracts/mock/MockAuction.sol index 61449c33..af653990 100644 --- a/protocol/contracts/mock/MockAuction.sol +++ b/protocol/contracts/mock/MockAuction.sol @@ -18,12 +18,46 @@ pragma solidity ^0.5.17; pragma experimental ABIEncoderV2; import "../dao/Auction.sol"; +import "../dao/Market.sol"; import "./MockState.sol"; +import "./MockMarket.sol"; -contract MockAuction is Auction { +contract MockAuction is Auction, MockState, Market { constructor () public { } function settleCouponAuctionE() external returns (bool) { - return super.settleCouponAuction(); + return settleCouponAuction(); + } + + function initCouponAuctionE(address auction) external { + super.initCouponAuction(auction); + } + + function getCouponAuctionBidsE() external returns (uint256) { + return super.getCouponAuctionBids(); + } + + function getCouponAuctionMinExpiryE() external returns (uint256) { + return super.getCouponAuctionMinExpiry(); + } + + function getCouponAuctionMaxExpiryE() external returns (uint256) { + return super.getCouponAuctionMaxExpiry(); + } + + function getCouponAuctionMinYieldE() external returns (uint256) { + return super.getCouponAuctionMinYield(); + } + + function getCouponAuctionMaxYieldE() external returns (uint256) { + return super.getCouponAuctionMaxYield(); + } + + function getCouponAuctionMinDollarAmountE() external returns (uint256) { + return super.getCouponAuctionMinDollarAmount(); + } + + function getCouponAuctionMaxDollarAmountE() external returns (uint256) { + return super.getCouponAuctionMaxDollarAmount(); } } diff --git a/protocol/contracts/mock/MockState.sol b/protocol/contracts/mock/MockState.sol index b5becb40..89af18bd 100644 --- a/protocol/contracts/mock/MockState.sol +++ b/protocol/contracts/mock/MockState.sol @@ -119,80 +119,6 @@ contract MockState is Setters { super.eliminateOutstandingCoupons(epoch); } - function initCouponAuctionE(address auction) external { - super.initCouponAuction(auction); - } - - function cancelCouponAuctionAtEpochE(uint256 epoch) external { - super.cancelCouponAuctionAtEpoch(epoch); - } - - function finishCouponAuctionAtEpochE(uint256 epoch) external { - super.finishCouponAuctionAtEpoch(epoch); - } - - function setCouponBidderStateE(address bidder, uint256 couponEpochExpiry, uint256 dollarAmount, uint256 maxCouponAmount) external { - super.setCouponBidderState(bidder, couponEpochExpiry, dollarAmount, maxCouponAmount); - } - - function setCouponBidderStateSelectedE(address bidder) external { - super.setCouponBidderStateSelected(bidder); - } - - function setCouponBidderStateRejectedE(address bidder) external { - super.setCouponBidderStateRejected(bidder); - } - - function setCouponBidderStateIndexE(uint256 index, address bidder) external { - super.setCouponBidderStateIndex(index, bidder); - } - - function incrementCouponAuctionBidsE() external { - super.incrementCouponAuctionBids(); - } - - function setCouponAuctionRelYieldE(uint256 yield) external { - super.setCouponAuctionRelYield(yield); - } - function setCouponAuctionRelExpiryE(uint256 couponEpochExpiry) external { - super.setCouponAuctionRelExpiry(couponEpochExpiry); - } - - function setCouponAuctionRelDollarAmountE(uint256 couponDollarAmount) external { - super.setCouponAuctionRelDollarAmount(couponDollarAmount); - } - - function setMinExpiryFilledE(uint256 minExpiryFilled) external { - super.setMinExpiryFilled(minExpiryFilled); - } - - function setMaxExpiryFilledE(uint256 maxExpiryFilled) external { - super.setMaxExpiryFilled(maxExpiryFilled); - } - - function setAvgExpiryFilledE(uint256 avgExpiryFilled) external { - super.setAvgExpiryFilled(avgExpiryFilled); - } - - function setMinYieldFilledE(uint256 minYieldFilled) external { - super.setMinYieldFilled(minYieldFilled); - } - - function setMaxYieldFilledE(uint256 maxYieldFilled) external { - super.setMaxYieldFilled(maxYieldFilled); - } - - function setAvgYieldFilledE(uint256 avgYieldFilled) external { - super.setAvgYieldFilled(avgYieldFilled); - } - - function setBidToCoverE(uint256 bidToCover) external { - super.setBidToCover(bidToCover); - } - - function setTotalFilledE(uint256 totalFilled) external { - super.setTotalFilled(totalFilled); - } /** * Governance diff --git a/protocol/test/dao/Auction.test.js b/protocol/test/dao/Auction.test.js new file mode 100644 index 00000000..83b6f454 --- /dev/null +++ b/protocol/test/dao/Auction.test.js @@ -0,0 +1,138 @@ +const { accounts, contract } = require('@openzeppelin/test-environment'); + +const { BN, expectRevert, time } = require('@openzeppelin/test-helpers'); +const { expect } = require('chai'); + +const MockAuction = contract.fromArtifact('MockAuction'); +const MockMarket = contract.fromArtifact('MockMarket'); +const Dollar = contract.fromArtifact('Dollar'); +const DAO = contract.fromArtifact('IDAO'); + +describe('Auction', function () { + const [ ownerAddress, poolAddress, userAddress, userAddress2, userAddress3, userAddress4 ] = accounts; + + beforeEach(async function () { + this.auction = await MockAuction.new({from: ownerAddress}); + this.market = await MockMarket.new(poolAddress, {from: ownerAddress, gas: 8000000}); + this.dollar = await Dollar.at(await this.market.dollar()); + + await this.auction.incrementEpochE(); + await this.market.stepE(); + await this.market.mintToE(userAddress, 1000000); + await this.dollar.approve(this.market.address, 1000000, {from: userAddress}); + }); + + describe('when settling auction', function () { + describe('auction is not finished and not canceled', function () { + beforeEach(async function () { + await this.auction.incrementTotalDebtE(100000); + await this.auction.initCouponAuctionE(this.auction.address); + + }); + + //it('is able to settle auction and generated internals and deincrement debt', async function () { + it('getCouponAuctionMinExpiryE', async function () { + // add some bidders + this.result = await this.auction.placeCouponAuctionBid(20, 1000, 50000, {from: userAddress}); + this.result1 = await this.auction.placeCouponAuctionBid(5, 2000, 50000, {from: userAddress2}); + //thise bidders will be rejected + this.result2 = await this.auction.placeCouponAuctionBid(1000, 100, 50000, {from: userAddress3}); + this.result3 = await this.auction.placeCouponAuctionBid(100990, 100, 50000, {from: userAddress4}); + await this.auction.settleCouponAuctionE(); + //expect(await this.auction.getCouponAuctionBidsE.call()).to.be.bignumber.equal(new BN(1)); + expect(await this.auction.getCouponAuctionMinExpiryE.call()).to.be.bignumber.equal(new BN(1)); + /*expect(await this.auction.getCouponAuctionMaxExpiryE.call()).to.be.bignumber.equal(new BN(1)); + expect(await this.auction.getCouponAuctionMinYieldE.call()).to.be.bignumber.equal(new BN(1)); + expect(await this.auction.getCouponAuctionMaxYieldE.call()).to.be.bignumber.equal(new BN(1)); + expect(await this.auction.getCouponAuctionMinDollarAmountE.call()).to.be.bignumber.equal(new BN(1)); + expect(await this.auction.getCouponAuctionMaxDollarAmountE.call()).to.be.bignumber.equal(new BN(1)); + */ + + expect(await this.auction.getMinExpiryFilled(1)).to.be.bignumber.equal(new BN(1)); + expect(await this.auction.getMaxExpiryFilled(1)).to.be.bignumber.equal(new BN(1)); + expect(await this.auction.getAvgExpiryFilled(1)).to.be.bignumber.equal(new BN(1)); + expect(await this.auction.getMinYieldFilled(1)).to.be.bignumber.equal(new BN(1)); + expect(await this.auction.getMaxYieldFilled(1)).to.be.bignumber.equal(new BN(1)); + expect(await this.auction.getAvgYieldFilled(1)).to.be.bignumber.equal(new BN(1)); + expect(await this.auction.getBidToCover(1)).to.be.bignumber.equal(new BN(1)); + expect(await this.auction.getTotalFilled(1)).to.be.bignumber.equal(new BN(1)); + }); + + it('getCouponAuctionMaxExpiryE', async function () { + // add some bidders + this.result = await this.auction.placeCouponAuctionBid(20, 1000, 50000, {from: userAddress}); + this.result1 = await this.auction.placeCouponAuctionBid(5, 2000, 50000, {from: userAddress2}); + //thise bidders will be rejected + this.result2 = await this.auction.placeCouponAuctionBid(1000, 100, 50000, {from: userAddress3}); + this.result3 = await this.auction.placeCouponAuctionBid(100990, 100, 50000, {from: userAddress4}); + //await this.auction.settleCouponAuctionE(); + //expect(await this.auction.getCouponAuctionBidsE.call()).to.be.bignumber.equal(new BN(1)); + //expect(await this.auction.getCouponAuctionMinExpiryE.call()).to.be.bignumber.equal(new BN(1)); + expect(await this.auction.getCouponAuctionMaxExpiryE.call()).to.be.bignumber.equal(new BN(1)); + //expect(await this.auction.getCouponAuctionMinYieldE.call()).to.be.bignumber.equal(new BN(1)); + //expect(await this.auction.getCouponAuctionMaxYieldE.call()).to.be.bignumber.equal(new BN(1)); + //expect(await this.auction.getCouponAuctionMinDollarAmountE.call()).to.be.bignumber.equal(new BN(1)); + + }); + + + it('getCouponAuctionMinYieldE', async function () { + // add some bidders + this.result = await this.auction.placeCouponAuctionBid(20, 1000, 50000, {from: userAddress}); + this.result1 = await this.auction.placeCouponAuctionBid(5, 2000, 50000, {from: userAddress2}); + //thise bidders will be rejected + this.result2 = await this.auction.placeCouponAuctionBid(1000, 100, 50000, {from: userAddress3}); + this.result3 = await this.auction.placeCouponAuctionBid(100990, 100, 50000, {from: userAddress4}); + //await this.auction.settleCouponAuctionE(); + //expect(await this.auction.getCouponAuctionBidsE.call()).to.be.bignumber.equal(new BN(1)); + //expect(await this.auction.getCouponAuctionMinExpiryE.call()).to.be.bignumber.equal(new BN(1)); + //expect(await this.auction.getCouponAuctionMaxExpiryE.call()).to.be.bignumber.equal(new BN(1)); + expect(await this.auction.getCouponAuctionMinYieldE.call()).to.be.bignumber.equal(new BN(1)); + //expect(await this.auction.getCouponAuctionMaxYieldE.call()).to.be.bignumber.equal(new BN(1)); + //expect(await this.auction.getCouponAuctionMinDollarAmountE.call()).to.be.bignumber.equal(new BN(1)); + //expect(await this.auction.getCouponAuctionMaxDollarAmountE.call()).to.be.bignumber.equal(new BN(1)); + }); + + + it('getCouponAuctionMaxYieldE', async function () { + // add some bidders + this.result = await this.auction.placeCouponAuctionBid(20, 1000, 50000, {from: userAddress}); + this.result1 = await this.auction.placeCouponAuctionBid(5, 2000, 50000, {from: userAddress2}); + //thise bidders will be rejected + this.result2 = await this.auction.placeCouponAuctionBid(1000, 100, 50000, {from: userAddress3}); + this.result3 = await this.auction.placeCouponAuctionBid(100990, 100, 50000, {from: userAddress4}); + //await this.auction.settleCouponAuctionE(); + expect(await this.auction.getCouponAuctionMaxYieldE.call()).to.be.bignumber.equal(new BN(1)); + }); + + it('getCouponAuctionMinDollarAmountE', async function () { + // add some bidders + this.result = await this.auction.placeCouponAuctionBid(20, 1000, 50000, {from: userAddress}); + this.result1 = await this.auction.placeCouponAuctionBid(5, 2000, 50000, {from: userAddress2}); + //thise bidders will be rejected + this.result2 = await this.auction.placeCouponAuctionBid(1000, 100, 50000, {from: userAddress3}); + this.result3 = await this.auction.placeCouponAuctionBid(100990, 100, 50000, {from: userAddress4}); + //await this.auction.settleCouponAuctionE(); + expect(await this.auction.getCouponAuctionMinDollarAmountE.call()).to.be.bignumber.equal(new BN(1)); + }); + + + it('getCouponAuctionMaxDollarAmountE', async function () { + // add some bidders + this.result = await this.auction.placeCouponAuctionBid(20, 1000, 50000, {from: userAddress}); + this.result1 = await this.auction.placeCouponAuctionBid(5, 2000, 50000, {from: userAddress2}); + //thise bidders will be rejected + this.result2 = await this.auction.placeCouponAuctionBid(1000, 100, 50000, {from: userAddress3}); + this.result3 = await this.auction.placeCouponAuctionBid(100990, 100, 50000, {from: userAddress4}); + //await this.auction.settleCouponAuctionE(); + expect(await this.auction.getCouponAuctionMaxDollarAmountE.call()).to.be.bignumber.equal(new BN(1)); + }); + }); + describe('auction is finished', function () { + + }); + describe('auction is canceled', function () { + + }); + }); +}); \ No newline at end of file diff --git a/protocol/test/dao/Auction.tests.js b/protocol/test/dao/Auction.tests.js deleted file mode 100644 index 4726e589..00000000 --- a/protocol/test/dao/Auction.tests.js +++ /dev/null @@ -1,26 +0,0 @@ -const { accounts, contract } = require('@openzeppelin/test-environment'); - -const { BN, expectRevert, time } = require('@openzeppelin/test-helpers'); -const { expect } = require('chai'); - -const MockAuction = contract.fromArtifact('MockAuction'); - -describe('Auction', function () { - const [ ownerAddress ] = accounts; - - beforeEach(async function () { - this.auction = await MockAuction.new({from: ownerAddress}); - }); - - describe('when settling auction', function () { - describe('auction is not finished and not canceled', function () { - - }); - describe('auction is finished', function () { - - }); - describe('auction is canceled', function () { - - }); - }); -}); \ No newline at end of file diff --git a/protocol/test/dao/State.test.js b/protocol/test/dao/State.test.js index e76b1cef..cb912335 100644 --- a/protocol/test/dao/State.test.js +++ b/protocol/test/dao/State.test.js @@ -752,270 +752,6 @@ describe('State', function () { }); }); - - describe('initCouponAuction', function () { - describe('when called', function () { - beforeEach('call', async function () { - await this.setters.initCouponAuctionE(this.auction); - }); - - it('has auction set', async function () { - expect(await this.setters.getCouponAuctionAtEpoch(1)).to.be.equal(this.auction); - }); - }); - }); - - describe('cancelCouponAuctionAtEpoch', function () { - describe('when called', function () { - beforeEach('call', async function () { - await this.setters.initCouponAuctionE(this.auction); - await this.setters.cancelCouponAuctionAtEpochE(1); - }); - - it('has auction cancled', async function () { - expect(await this.setters.getCouponAuctionAtEpoch(1)).canceled.to.be.equal(true); - }); - }); - }); - - describe('finishCouponAuctionAtEpoch', function () { - describe('when called', function () { - beforeEach('call', async function () { - await this.setters.initCouponAuctionE(this.auction); - await this.setters.finishCouponAuctionAtEpochE(1); - }); - - it('has auction finished', async function () { - expect(await this.setters.getCouponAuctionAtEpoch(1)).finished.to.be.equal(true); - }); - }); - }); - - describe('setCouponBidderState', function () { - describe('when called', function () { - beforeEach('call', async function () { - await this.setters.initCouponAuctionE(this.auction); - await this.setters.setCouponBidderStateE(userAddress, 1, 100, 500); - }); - - it('has bidder state set', async function () { - expect(await this.setters.getCouponBidderState(userAddress)).couponExpiryEpoch.to.be.equal(new BN(1)); - expect(await this.setters.getCouponBidderState(userAddress)).dollarAmount.to.be.equal(new BN(100)); - expect(await this.setters.getCouponBidderState(userAddress)).couponAmount.to.be.equal(new BN(500)); - expect(await this.setters.getCouponBidderState(userAddress)).couponAmount.to.be.equal(userAddress); - }); - }); - }); - - describe('setCouponBidderStateSelected', function () { - describe('when called', function () { - beforeEach('call', async function () { - await this.setters.initCouponAuctionE(this.auction); - await this.setters.setCouponBidderStateE(userAddress, 1, 100, 500); - await this.setters.setCouponBidderStateSelectedE(userAddress); - }); - - it('has bidder bid selected', async function () { - expect(await this.setters.getCouponBidderState(userAddress)).selected.to.be.equal(true); - }); - }); - }); - - describe('setCouponBidderStateRejected', function () { - describe('when called', function () { - beforeEach('call', async function () { - await this.setters.initCouponAuctionE(this.auction); - await this.setters.setCouponBidderStateE(userAddress, 1, 100, 500); - await this.setters.setCouponBidderStateRejectedE(userAddress); - }); - - it('has bidder bid rejected', async function () { - expect(await this.setters.getCouponBidderState(userAddress)).rejected.to.be.equal(true); - }); - }); - }); - describe('setCouponBidderStateIndex', function () { - describe('when called', function () { - beforeEach('call', async function () { - await this.setters.initCouponAuctionE(this.auction); - await this.setters.setCouponBidderStateE(userAddress, 1, 100, 500); - await this.setters.setCouponBidderStateIndexE(1, userAddress); - }); - - it('has bidder state index at specific index set to bidder', async function () { - expect(await this.setters.getCouponBidderStateIndexE(1)).to.be.equal(userAddress); - }); - }); - }); - - describe('incrementCouponAuctionBids', function () { - describe('when called', function () { - beforeEach('call', async function () { - await this.setters.initCouponAuctionE(this.auction); - await this.setters.incrementCouponAuctionBidsE(); - }); - - it('has bidder state index at specific index set to bidder', async function () { - expect(await this.setters.getCouponAuctionBids()).to.be.equal(new BN(1)); - }); - }); - }); - - describe('setCouponAuctionRelYield', function () { - describe('when called', function () { - beforeEach('call', async function () { - await this.setters.initCouponAuctionE(this.auction); - await this.setters.setCouponAuctionRelYieldE(10); - await this.setters.setCouponAuctionRelYieldE(100); - }); - - it('has min yield set to 10', async function () { - expect(await this.setters.getCouponAuctionMinYield()).to.be.equal(new BN(10)); - }); - it('has max yield set to 100', async function () { - expect(await this.setters.getCouponAuctionMaxYield()).to.be.equal(new BN(100)); - }); - }); - }); - - describe('setCouponAuctionRelExpiry', function () { - describe('when called', function () { - beforeEach('call', async function () { - await this.setters.initCouponAuctionE(this.auction); - await this.setters.setCouponAuctionRelExpiryE(10); - await this.setters.setCouponAuctionRelExpiryE(100); - }); - - it('has min expiry set to 10', async function () { - expect(await this.setters.getCouponAuctionMinExpiry()).to.be.equal(new BN(10)); - }); - it('has max expiry set to 100', async function () { - expect(await this.setters.getCouponAuctionMaxExpiry()).to.be.equal(new BN(100)); - }); - }); - }); - - describe('setCouponAuctionRelDollarAmount', function () { - describe('when called', function () { - beforeEach('call', async function () { - await this.setters.initCouponAuctionE(this.auction); - await this.setters.setCouponAuctionRelDollarAmountE(10); - await this.setters.setCouponAuctionRelDollarAmountE(100); - }); - - it('has min dollar amount set to 10', async function () { - expect(await this.setters.getCouponAuctionMinDollarAmount()).to.be.equal(new BN(10)); - }); - it('has max dollar amount set to 100', async function () { - expect(await this.setters.getCouponAuctionMaxDollarAmount()).to.be.equal(new BN(100)); - }); - }); - }); - - describe('setMinExpiryFilled', function () { - describe('when called', function () { - beforeEach('call', async function () { - await this.setters.initCouponAuctionE(this.auction); - await this.setters.setMinExpiryFilledE(10); - }); - - it('has min expiry filled set to 10', async function () { - expect(await this.setters.getCouponAuctionAtEpoch(1)).minExpiryFilled.to.be.equal(new BN(10)); - }); - }); - }); - - describe('setMaxExpiryFilled', function () { - describe('when called', function () { - beforeEach('call', async function () { - await this.setters.initCouponAuctionE(this.auction); - await this.setters.setMaxExpiryFilledE(10); - }); - - it('has max expiry filled set to 10', async function () { - expect(await this.setters.getCouponAuctionAtEpoch(1)).maxExpiryFilled.to.be.equal(new BN(10)); - }); - }); - }); - - describe('setAvgExpiryFilled', function () { - describe('when called', function () { - beforeEach('call', async function () { - await this.setters.initCouponAuctionE(this.auction); - await this.setters.setAvgExpiryFilled(10); - }); - - it('has avg expiry filled set to 10', async function () { - expect(await this.setters.getCouponAuctionAtEpoch(1)).avgExpiryFilled.to.be.equal(new BN(10)); - }); - }); - }); - - describe('setMinYieldFilled', function () { - describe('when called', function () { - beforeEach('call', async function () { - await this.setters.initCouponAuctionE(this.auction); - await this.setters.setMinYieldFilledE(10); - }); - - it('has min yield filled set to 10', async function () { - expect(await this.setters.getCouponAuctionAtEpoch(1)).minYieldFilled.to.be.equal(new BN(10)); - }); - }); - }); - - describe('setMaxYieldFilled', function () { - describe('when called', function () { - beforeEach('call', async function () { - await this.setters.initCouponAuctionE(this.auction); - await this.setters.setMaxYieldFilledE(10); - }); - - it('has max yield filled set to 10', async function () { - expect(await this.setters.getCouponAuctionAtEpoch(1)).maxYieldFilled.to.be.equal(new BN(10)); - }); - }); - }); - - describe('setAvgYieldFilled', function () { - describe('when called', function () { - beforeEach('call', async function () { - await this.setters.initCouponAuctionE(this.auction); - await this.setters.setAvgYieldFilledE(10); - }); - - it('has avg yield filled set to 10', async function () { - expect(await this.setters.getCouponAuctionAtEpoch(1)).avgYieldFilled.to.be.equal(new BN(10)); - }); - }); - }); - - describe('setBidToCover', function () { - describe('when called', function () { - beforeEach('call', async function () { - await this.setters.initCouponAuctionE(this.auction); - await this.setters.setBidToCoverE(10); - }); - - it('has bid to cover set to 10', async function () { - expect(await this.setters.getBidToCover(1)).bidToCover.to.be.equal(new BN(10)); - }); - }); - }); - - describe('setTotalFilled', function () { - describe('when called', function () { - beforeEach('call', async function () { - await this.setters.initCouponAuctionE(this.auction); - await this.setters.setTotalFilledE(100); - }); - - it('has total bids filled to 100', async function () { - expect(await this.setters.getTotalFilled(1)).totalFilled.to.be.equal(new BN(100)); - }); - }); - }); - /** * Governance */ From d7d69650a35b45c9e7afe547f7599d9250e9b060 Mon Sep 17 00:00:00 2001 From: cinquemb <1519017+cinquemb@users.noreply.github.com> Date: Thu, 7 Jan 2021 02:40:22 -0500 Subject: [PATCH 25/47] finsihed with auction test --- protocol/contracts/dao/Auction.sol | 108 ++++++++++------- protocol/contracts/dao/Setters.sol | 6 + protocol/contracts/dao/State.sol | 2 +- protocol/contracts/mock/MockAuction.sol | 18 ++- protocol/test/dao/Auction.test.js | 147 +++++++++--------------- 5 files changed, 145 insertions(+), 136 deletions(-) diff --git a/protocol/contracts/dao/Auction.sol b/protocol/contracts/dao/Auction.sol index 1809ed88..d1fc4619 100644 --- a/protocol/contracts/dao/Auction.sol +++ b/protocol/contracts/dao/Auction.sol @@ -19,6 +19,7 @@ pragma experimental ABIEncoderV2; import "@openzeppelin/contracts/math/SafeMath.sol"; import "./Comptroller.sol"; +import "./Market.sol"; import "../Constants.sol"; import "../external/Decimal.sol"; @@ -34,23 +35,23 @@ contract Auction is Comptroller { uint256 private minExpiryFilled = 10000000000000000000; uint256 private maxExpiryFilled = 0; uint256 private sumExpiryFilled = 0; - uint256 private minYieldFilled = 10000000000000000000; - uint256 private maxYieldFilled = 0; - uint256 private sumYieldFilled = 0; + Decimal.D256 private minYieldFilled = Decimal.D256(10000000000000000000); + Decimal.D256 private maxYieldFilled = Decimal.zero(); + Decimal.D256 private sumYieldFilled = Decimal.zero(); - function sortBidsByDistance(Epoch.CouponBidderState[] memory bids) internal returns(Epoch.CouponBidderState[] memory) { - quickSort(bids, uint256(0), uint256(bids.length - 1)); + function sortBidsByDistance(Epoch.CouponBidderState[] storage bids) internal returns(Epoch.CouponBidderState[] storage) { + quickSort(bids, int(0), int(bids.length - 1)); return bids; } - function quickSort(Epoch.CouponBidderState[] memory arr, uint256 left, uint256 right) internal { - uint256 i = left; - uint256 j = right; + function quickSort(Epoch.CouponBidderState[] memory arr, int left, int right) internal { + int i = left; + int j = right; if(i==j) return; - uint256 pivot = arr[uint256(left + (right - left) / 2)].distance; + Decimal.D256 memory pivot = arr[uint256(left + (right - left) / 2)].distance; while (i <= j) { - while (arr[uint256(i)].distance < pivot) i++; - while (pivot < arr[uint256(j)].distance) j--; + while (arr[uint256(i)].distance.lessThan(pivot)) i++; + while (pivot.lessThan(arr[uint256(j)].distance)) j--; if (i <= j) { (arr[uint256(i)], arr[uint256(j)]) = (arr[uint256(j)], arr[uint256(i)]); i++; @@ -63,68 +64,77 @@ contract Auction is Comptroller { quickSort(arr, i, right); } - function sqrt(uint256 x) internal pure returns (uint256 y) { - uint256 z = x.add(1).div(2); + function sqrt(Decimal.D256 memory x) internal pure returns (Decimal.D256 memory y) { + Decimal.D256 memory z = x.add(1).div(2); y = x; - while (z < y) { + while (z.lessThan(y)) { y = z; z = x.div(z.add(z)).div(2); } + return y; } function settleCouponAuction() internal returns (bool success) { if (!isCouponAuctionFinished() && !isCouponAuctionCanceled()) { uint256 yieldRelNorm = getCouponAuctionMaxYield() - getCouponAuctionMinYield(); uint256 expiryRelNorm = getCouponAuctionMaxExpiry() - getCouponAuctionMinExpiry(); - uint256 dollarRelNorm = getCouponAuctionMaxDollarAmount() - getCouponAuctionMinDollarAmount(); + uint256 dollarRelNorm = getCouponAuctionMaxDollarAmount() - getCouponAuctionMinDollarAmount(); // loop over bids and compute distance for (uint256 i = 0; i < getCouponAuctionBids(); i++) { + Epoch.CouponBidderState storage bidder = getCouponBidderState(getCouponBidderStateIndex(i)); Decimal.D256 memory yieldRel = Decimal.ratio( Decimal.ratio( - getCouponBidderState(getCouponBidderStateIndex(i)).couponAmount, - getCouponBidderState(getCouponBidderStateIndex(i)).dollarAmount + bidder.couponAmount, + bidder.dollarAmount ).asUint256(), yieldRelNorm ); Decimal.D256 memory expiryRel = Decimal.ratio( - getCouponBidderState(getCouponBidderStateIndex(i)).couponExpiryEpoch, + bidder.couponExpiryEpoch, expiryRelNorm ); Decimal.D256 memory dollarRelMax = Decimal.ratio( - getCouponBidderState(getCouponBidderStateIndex(i)).dollarAmount, + bidder.dollarAmount, dollarRelNorm ); - uint256 dollarRel = 1 - dollarRelMax.asUint256(); + Decimal.D256 memory dollarRel = (Decimal.one().add(Decimal.one())).sub(dollarRelMax); Decimal.D256 memory yieldRelSquared = yieldRel.pow(2); Decimal.D256 memory expiryRelSquared = expiryRel.pow(2); - Decimal.D256 memory dollarRelSquared = Decimal.D256(dollarRel).pow(2); + Decimal.D256 memory dollarRelSquared = dollarRel.pow(2); - uint256 sumSquared = yieldRelSquared.add(expiryRelSquared).add(dollarRelSquared).asUint256(); - uint256 distance = sqrt(sumSquared); - getCouponBidderState(getCouponBidderStateIndex(i)).distance = distance; - bids.push(getCouponBidderState(getCouponBidderStateIndex(i))); - } + Decimal.D256 memory sumOfSquared = yieldRelSquared.add(expiryRelSquared).add(dollarRelSquared); + Decimal.D256 memory distance; + if (sumOfSquared.greaterThan(Decimal.zero())) { + distance = sqrt(sumOfSquared); + } else { + distance = Decimal.zero(); + } - // sort bids - sortBidsByDistance(bids); + setCouponBidderStateDistance(getCouponBidderStateIndex(i), distance); + bidder = getCouponBidderState(getCouponBidderStateIndex(i)); + bids.push(bidder); + } + // sort bids + bids = sortBidsByDistance(bids); // assign coupons until totalDebt filled, reject the rest for (uint256 i = 0; i < bids.length; i++) { if (totalDebt() >= bids[i].dollarAmount) { if (!getCouponBidderStateRejected(bids[i].bidder) && !getCouponBidderStateRejected(bids[i].bidder)) { - uint256 yield = bids[i].couponAmount.div( + Decimal.D256 memory yield = Decimal.ratio( + bids[i].couponAmount, bids[i].dollarAmount ); - if (yield < minYieldFilled) { + if (yield.lessThan(minYieldFilled)) { minYieldFilled = yield; - } else if (yield > maxYieldFilled) { + } else if (yield.greaterThan(maxYieldFilled)) { maxYieldFilled = yield; } @@ -149,18 +159,30 @@ contract Auction is Comptroller { } // set auction internals - uint256 avgYieldFilled = sumYieldFilled.div(totalFilled); - uint256 avgExpiryFilled = sumExpiryFilled.div(totalFilled); - uint256 bidToCover = bids.length.div(totalFilled); - - setMinExpiryFilled(minExpiryFilled); - setMaxExpiryFilled(maxExpiryFilled); - setAvgExpiryFilled(avgExpiryFilled); - setMinYieldFilled(minYieldFilled); - setMaxYieldFilled(maxYieldFilled); - setAvgYieldFilled(avgYieldFilled); - setBidToCover(bidToCover); - setTotalFilled(totalFilled); + if (totalFilled > 0) { + Decimal.D256 memory avgYieldFilled = Decimal.ratio( + sumYieldFilled.asUint256(), + totalFilled + ); + Decimal.D256 memory avgExpiryFilled = Decimal.ratio( + sumExpiryFilled, + totalFilled + ); + Decimal.D256 memory bidToCover = Decimal.ratio( + bids.length, + totalFilled + ); + + setMinExpiryFilled(minExpiryFilled); + setMaxExpiryFilled(maxExpiryFilled); + setAvgExpiryFilled(avgExpiryFilled.asUint256()); + setMinYieldFilled(minYieldFilled.asUint256()); + setMaxYieldFilled(maxYieldFilled.asUint256()); + setAvgYieldFilled(avgYieldFilled.asUint256()); + setBidToCover(bidToCover.asUint256()); + setTotalFilled(totalFilled); + } + return true; } else { diff --git a/protocol/contracts/dao/Setters.sol b/protocol/contracts/dao/Setters.sol index 3e3fa719..0bd945d9 100644 --- a/protocol/contracts/dao/Setters.sol +++ b/protocol/contracts/dao/Setters.sol @@ -175,6 +175,12 @@ contract Setters is State, Getters { bidderState.dollarAmount = dollarAmount; bidderState.couponAmount = maxCouponAmount; bidderState.bidder = bidder; + + _state.epochs[epoch()].auction.couponBidderState[bidder] = bidderState; + } + + function setCouponBidderStateDistance(address bidder, Decimal.D256 memory distance) internal { + _state.epochs[epoch()].auction.couponBidderState[bidder].distance = distance; } function setCouponBidderStateSelected(address bidder) internal { diff --git a/protocol/contracts/dao/State.sol b/protocol/contracts/dao/State.sol index 9dadba43..8aa7197d 100644 --- a/protocol/contracts/dao/State.sol +++ b/protocol/contracts/dao/State.sol @@ -56,9 +56,9 @@ contract Epoch { bool selected; bool rejected; address bidder; - uint256 distance; uint256 dollarAmount; uint256 couponAmount; + Decimal.D256 distance; uint256 couponExpiryEpoch; } diff --git a/protocol/contracts/mock/MockAuction.sol b/protocol/contracts/mock/MockAuction.sol index af653990..7465a00a 100644 --- a/protocol/contracts/mock/MockAuction.sol +++ b/protocol/contracts/mock/MockAuction.sol @@ -21,14 +21,28 @@ import "../dao/Auction.sol"; import "../dao/Market.sol"; import "./MockState.sol"; import "./MockMarket.sol"; +import "./MockComptroller.sol"; -contract MockAuction is Auction, MockState, Market { - constructor () public { } + +contract MockAuction is MockState, MockComptroller, Auction, Market { + constructor(address pool) MockComptroller(pool) public { } + + function stepE() external { + Market.step(); + } function settleCouponAuctionE() external returns (bool) { return settleCouponAuction(); } + function cancelCouponAuctionAtEpochE(uint256 epoch) external { + super.cancelCouponAuctionAtEpoch(epoch); + } + + function finishCouponAuctionAtEpochE(uint256 epoch) external { + super.finishCouponAuctionAtEpoch(epoch); + } + function initCouponAuctionE(address auction) external { super.initCouponAuction(auction); } diff --git a/protocol/test/dao/Auction.test.js b/protocol/test/dao/Auction.test.js index 83b6f454..0f8ff5e0 100644 --- a/protocol/test/dao/Auction.test.js +++ b/protocol/test/dao/Auction.test.js @@ -6,132 +6,99 @@ const { expect } = require('chai'); const MockAuction = contract.fromArtifact('MockAuction'); const MockMarket = contract.fromArtifact('MockMarket'); const Dollar = contract.fromArtifact('Dollar'); -const DAO = contract.fromArtifact('IDAO'); describe('Auction', function () { const [ ownerAddress, poolAddress, userAddress, userAddress2, userAddress3, userAddress4 ] = accounts; beforeEach(async function () { - this.auction = await MockAuction.new({from: ownerAddress}); - this.market = await MockMarket.new(poolAddress, {from: ownerAddress, gas: 8000000}); - this.dollar = await Dollar.at(await this.market.dollar()); + this.auction = await MockAuction.new(poolAddress, {from: ownerAddress, gas: 8000000}); + this.dollar = await Dollar.at(await this.auction.dollar()); await this.auction.incrementEpochE(); - await this.market.stepE(); - await this.market.mintToE(userAddress, 1000000); - await this.dollar.approve(this.market.address, 1000000, {from: userAddress}); + await this.auction.stepE(); + await this.auction.mintToE(userAddress, 1000000); + await this.auction.mintToE(userAddress2, 1000000); + await this.auction.mintToE(userAddress3, 1000000); + await this.auction.mintToE(userAddress4, 1000000); + await this.dollar.approve(this.auction.address, 1000000, {from: userAddress}); + await this.dollar.approve(this.auction.address, 1000000, {from: userAddress2}); + await this.dollar.approve(this.auction.address, 1000000, {from: userAddress3}); + await this.dollar.approve(this.auction.address, 1000000, {from: userAddress4}); }); describe('when settling auction', function () { describe('auction is not finished and not canceled', function () { beforeEach(async function () { - await this.auction.incrementTotalDebtE(100000); + await this.auction.incrementTotalDebtE(4000); await this.auction.initCouponAuctionE(this.auction.address); - }); - //it('is able to settle auction and generated internals and deincrement debt', async function () { - it('getCouponAuctionMinExpiryE', async function () { + + it('is able to settle auction and generated internals and deincrement debt', async function () { // add some bidders this.result = await this.auction.placeCouponAuctionBid(20, 1000, 50000, {from: userAddress}); this.result1 = await this.auction.placeCouponAuctionBid(5, 2000, 50000, {from: userAddress2}); //thise bidders will be rejected - this.result2 = await this.auction.placeCouponAuctionBid(1000, 100, 50000, {from: userAddress3}); - this.result3 = await this.auction.placeCouponAuctionBid(100990, 100, 50000, {from: userAddress4}); - await this.auction.settleCouponAuctionE(); - //expect(await this.auction.getCouponAuctionBidsE.call()).to.be.bignumber.equal(new BN(1)); - expect(await this.auction.getCouponAuctionMinExpiryE.call()).to.be.bignumber.equal(new BN(1)); - /*expect(await this.auction.getCouponAuctionMaxExpiryE.call()).to.be.bignumber.equal(new BN(1)); - expect(await this.auction.getCouponAuctionMinYieldE.call()).to.be.bignumber.equal(new BN(1)); - expect(await this.auction.getCouponAuctionMaxYieldE.call()).to.be.bignumber.equal(new BN(1)); - expect(await this.auction.getCouponAuctionMinDollarAmountE.call()).to.be.bignumber.equal(new BN(1)); - expect(await this.auction.getCouponAuctionMaxDollarAmountE.call()).to.be.bignumber.equal(new BN(1)); - */ - - expect(await this.auction.getMinExpiryFilled(1)).to.be.bignumber.equal(new BN(1)); - expect(await this.auction.getMaxExpiryFilled(1)).to.be.bignumber.equal(new BN(1)); - expect(await this.auction.getAvgExpiryFilled(1)).to.be.bignumber.equal(new BN(1)); - expect(await this.auction.getMinYieldFilled(1)).to.be.bignumber.equal(new BN(1)); - expect(await this.auction.getMaxYieldFilled(1)).to.be.bignumber.equal(new BN(1)); - expect(await this.auction.getAvgYieldFilled(1)).to.be.bignumber.equal(new BN(1)); + this.result2 = await this.auction.placeCouponAuctionBid(1000, 900, 50000, {from: userAddress3}); + this.result3 = await this.auction.placeCouponAuctionBid(100990, 900, 50000, {from: userAddress4}); + this.auction_settlement = this.auction.settleCouponAuctionE(); + + + expect(await this.auction.getCouponAuctionBidsE.call()).to.be.bignumber.equal(new BN(4)); + expect(await this.auction.getCouponAuctionMinExpiryE.call()).to.be.bignumber.equal(new BN(6)); + expect(await this.auction.getCouponAuctionMaxExpiryE.call()).to.be.bignumber.equal(new BN(100991)); + expect(await this.auction.getCouponAuctionMinYieldE.call()).to.be.bignumber.equal(new BN(25)); + expect(await this.auction.getCouponAuctionMaxYieldE.call()).to.be.bignumber.equal(new BN(55)); + expect(await this.auction.getCouponAuctionMinDollarAmountE.call()).to.be.bignumber.equal(new BN(900)); + expect(await this.auction.getCouponAuctionMaxDollarAmountE.call()).to.be.bignumber.equal(new BN(2000)); + + expect(await this.auction.getMinExpiryFilled(1)).to.be.bignumber.equal(new BN(6)); + expect(await this.auction.getMaxExpiryFilled(1)).to.be.bignumber.equal(new BN(1001)); + //TODO getAvgYieldFilled + expect(await this.auction.getAvgExpiryFilled(1)).to.be.bignumber.equal(new BN(0)); + expect(await this.auction.getMinYieldFilled(1)).to.be.bignumber.equal(new BN(10)); + expect(await this.auction.getMaxYieldFilled(1)).to.be.bignumber.equal(new BN(55)); + //TODO getAvgYieldFilled + expect(await this.auction.getAvgYieldFilled(1)).to.be.bignumber.equal(new BN(0)); + //TODO getBidToCover expect(await this.auction.getBidToCover(1)).to.be.bignumber.equal(new BN(1)); - expect(await this.auction.getTotalFilled(1)).to.be.bignumber.equal(new BN(1)); + expect(await this.auction.getTotalFilled(1)).to.be.bignumber.equal(new BN(3)); }); + }); - it('getCouponAuctionMaxExpiryE', async function () { - // add some bidders - this.result = await this.auction.placeCouponAuctionBid(20, 1000, 50000, {from: userAddress}); - this.result1 = await this.auction.placeCouponAuctionBid(5, 2000, 50000, {from: userAddress2}); - //thise bidders will be rejected - this.result2 = await this.auction.placeCouponAuctionBid(1000, 100, 50000, {from: userAddress3}); - this.result3 = await this.auction.placeCouponAuctionBid(100990, 100, 50000, {from: userAddress4}); - //await this.auction.settleCouponAuctionE(); - //expect(await this.auction.getCouponAuctionBidsE.call()).to.be.bignumber.equal(new BN(1)); - //expect(await this.auction.getCouponAuctionMinExpiryE.call()).to.be.bignumber.equal(new BN(1)); - expect(await this.auction.getCouponAuctionMaxExpiryE.call()).to.be.bignumber.equal(new BN(1)); - //expect(await this.auction.getCouponAuctionMinYieldE.call()).to.be.bignumber.equal(new BN(1)); - //expect(await this.auction.getCouponAuctionMaxYieldE.call()).to.be.bignumber.equal(new BN(1)); - //expect(await this.auction.getCouponAuctionMinDollarAmountE.call()).to.be.bignumber.equal(new BN(1)); - + describe('auction is finished', function () { + beforeEach(async function () { + //finish the auction + await this.auction.incrementTotalDebtE(4000); + await this.auction.initCouponAuctionE(this.auction.address); + await this.auction.finishCouponAuctionAtEpochE(1); }); - - it('getCouponAuctionMinYieldE', async function () { + it('is able to not settle auction', async function () { // add some bidders this.result = await this.auction.placeCouponAuctionBid(20, 1000, 50000, {from: userAddress}); this.result1 = await this.auction.placeCouponAuctionBid(5, 2000, 50000, {from: userAddress2}); - //thise bidders will be rejected - this.result2 = await this.auction.placeCouponAuctionBid(1000, 100, 50000, {from: userAddress3}); - this.result3 = await this.auction.placeCouponAuctionBid(100990, 100, 50000, {from: userAddress4}); - //await this.auction.settleCouponAuctionE(); - //expect(await this.auction.getCouponAuctionBidsE.call()).to.be.bignumber.equal(new BN(1)); - //expect(await this.auction.getCouponAuctionMinExpiryE.call()).to.be.bignumber.equal(new BN(1)); - //expect(await this.auction.getCouponAuctionMaxExpiryE.call()).to.be.bignumber.equal(new BN(1)); - expect(await this.auction.getCouponAuctionMinYieldE.call()).to.be.bignumber.equal(new BN(1)); - //expect(await this.auction.getCouponAuctionMaxYieldE.call()).to.be.bignumber.equal(new BN(1)); - //expect(await this.auction.getCouponAuctionMinDollarAmountE.call()).to.be.bignumber.equal(new BN(1)); - //expect(await this.auction.getCouponAuctionMaxDollarAmountE.call()).to.be.bignumber.equal(new BN(1)); + this.auction_settlement = await this.auction.settleCouponAuctionE.call(); + expect(this.auction_settlement).to.be.equal(false); }); - - it('getCouponAuctionMaxYieldE', async function () { - // add some bidders - this.result = await this.auction.placeCouponAuctionBid(20, 1000, 50000, {from: userAddress}); - this.result1 = await this.auction.placeCouponAuctionBid(5, 2000, 50000, {from: userAddress2}); - //thise bidders will be rejected - this.result2 = await this.auction.placeCouponAuctionBid(1000, 100, 50000, {from: userAddress3}); - this.result3 = await this.auction.placeCouponAuctionBid(100990, 100, 50000, {from: userAddress4}); - //await this.auction.settleCouponAuctionE(); - expect(await this.auction.getCouponAuctionMaxYieldE.call()).to.be.bignumber.equal(new BN(1)); - }); - it('getCouponAuctionMinDollarAmountE', async function () { - // add some bidders - this.result = await this.auction.placeCouponAuctionBid(20, 1000, 50000, {from: userAddress}); - this.result1 = await this.auction.placeCouponAuctionBid(5, 2000, 50000, {from: userAddress2}); - //thise bidders will be rejected - this.result2 = await this.auction.placeCouponAuctionBid(1000, 100, 50000, {from: userAddress3}); - this.result3 = await this.auction.placeCouponAuctionBid(100990, 100, 50000, {from: userAddress4}); - //await this.auction.settleCouponAuctionE(); - expect(await this.auction.getCouponAuctionMinDollarAmountE.call()).to.be.bignumber.equal(new BN(1)); + }); + describe('auction is canceled', function () { + beforeEach(async function () { + //finish the auction + await this.auction.incrementTotalDebtE(4000); + await this.auction.initCouponAuctionE(this.auction.address); + await this.auction.cancelCouponAuctionAtEpochE(1); }); - - it('getCouponAuctionMaxDollarAmountE', async function () { + it('is able to not settle auction', async function () { // add some bidders this.result = await this.auction.placeCouponAuctionBid(20, 1000, 50000, {from: userAddress}); this.result1 = await this.auction.placeCouponAuctionBid(5, 2000, 50000, {from: userAddress2}); - //thise bidders will be rejected - this.result2 = await this.auction.placeCouponAuctionBid(1000, 100, 50000, {from: userAddress3}); - this.result3 = await this.auction.placeCouponAuctionBid(100990, 100, 50000, {from: userAddress4}); - //await this.auction.settleCouponAuctionE(); - expect(await this.auction.getCouponAuctionMaxDollarAmountE.call()).to.be.bignumber.equal(new BN(1)); + this.auction_settlement = await this.auction.settleCouponAuctionE.call(); + expect(this.auction_settlement).to.be.equal(false); }); - }); - describe('auction is finished', function () { - - }); - describe('auction is canceled', function () { }); }); From d48d484fff3336cf16879228473042860491b965 Mon Sep 17 00:00:00 2001 From: cinquemb <1519017+cinquemb@users.noreply.github.com> Date: Thu, 7 Jan 2021 05:56:47 -0500 Subject: [PATCH 26/47] need to finish/cancel prev auction in Regulator.step --- protocol/contracts/dao/Auction.sol | 3 ++- protocol/contracts/dao/Regulator.sol | 11 +++++------ protocol/contracts/dao/Setters.sol | 18 ++++++++++-------- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/protocol/contracts/dao/Auction.sol b/protocol/contracts/dao/Auction.sol index d1fc4619..735fad3c 100644 --- a/protocol/contracts/dao/Auction.sol +++ b/protocol/contracts/dao/Auction.sol @@ -154,7 +154,8 @@ contract Auction is Comptroller { totalFilled++; } } else { - setCouponBidderStateRejected(bids[i].bidder); + /* setCouponBidderStateRejected(bids[i].bidder); or just break and close the auction */ + break; } } diff --git a/protocol/contracts/dao/Regulator.sol b/protocol/contracts/dao/Regulator.sol index c0df9220..ac0caba3 100644 --- a/protocol/contracts/dao/Regulator.sol +++ b/protocol/contracts/dao/Regulator.sol @@ -34,12 +34,13 @@ contract Regulator is Comptroller, Auction { function step() internal { Decimal.D256 memory price = oracleCapture(); - if (price.greaterThan(Decimal.one())) { - Epoch.AuctionState storage auction = getCouponAuctionAtEpoch(epoch()); + //need to check previous epoch because by the time the Regulator.step function is fired, Bonding.step may have already incremented the epoch + Epoch.AuctionState storage auction = getCouponAuctionAtEpoch(epoch() - 1); + if (price.greaterThan(Decimal.one())) { //check for outstanding auction, if exists cancel it if (auction.couponAuction != address(0)){ - cancelCouponAuctionAtEpoch(epoch()); + cancelCouponAuctionAtEpoch(epoch() - 1); } growSupply(price); @@ -47,12 +48,10 @@ contract Regulator is Comptroller, Auction { } if (price.lessThan(Decimal.one())) { - Epoch.AuctionState storage auction = getCouponAuctionAtEpoch(epoch()); - //check for outstanding auction, if exists settle it and start a new one if (auction.couponAuction != address(0)){ bool isAuctionSettled = settleCouponAuction(); - finishCouponAuctionAtEpoch(epoch()); + finishCouponAuctionAtEpoch(epoch() - 1); } Auction newCouponAuction = new Auction(); initCouponAuction(address(newCouponAuction)); diff --git a/protocol/contracts/dao/Setters.sol b/protocol/contracts/dao/Setters.sol index 0bd945d9..2b95f0f4 100644 --- a/protocol/contracts/dao/Setters.sol +++ b/protocol/contracts/dao/Setters.sol @@ -150,14 +150,16 @@ contract Setters is State, Getters { } function initCouponAuction(address auction) internal { - _state.epochs[epoch()].auction.couponAuction = auction; - _state.epochs[epoch()].auction._totalBids = 0; - _state.epochs[epoch()].auction.minExpiry = 1000000000000000000000000; - _state.epochs[epoch()].auction.maxExpiry = 0; - _state.epochs[epoch()].auction.minYield = 1000000000000000000000000; - _state.epochs[epoch()].auction.maxYield = 0; - _state.epochs[epoch()].auction.minDollarAmount = 1000000000000000000000000; - _state.epochs[epoch()].auction.maxDollarAmount = 0; + if (_state.epochs[epoch()].auction.couponAuction == address(0)) { + _state.epochs[epoch()].auction.couponAuction = auction; + _state.epochs[epoch()].auction._totalBids = 0; + _state.epochs[epoch()].auction.minExpiry = 1000000000000000000000000; + _state.epochs[epoch()].auction.maxExpiry = 0; + _state.epochs[epoch()].auction.minYield = 1000000000000000000000000; + _state.epochs[epoch()].auction.maxYield = 0; + _state.epochs[epoch()].auction.minDollarAmount = 1000000000000000000000000; + _state.epochs[epoch()].auction.maxDollarAmount = 0; + } } function cancelCouponAuctionAtEpoch(uint256 epoch) internal { From d4fd56b6e69755992652ec4c0d8f5a83a2bb719e Mon Sep 17 00:00:00 2001 From: cinquemb <1519017+cinquemb@users.noreply.github.com> Date: Thu, 7 Jan 2021 06:15:49 -0500 Subject: [PATCH 27/47] use actual uint256 max for min initializations --- protocol/contracts/dao/Auction.sol | 4 ++-- protocol/contracts/dao/Setters.sol | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/protocol/contracts/dao/Auction.sol b/protocol/contracts/dao/Auction.sol index 735fad3c..32ba8da6 100644 --- a/protocol/contracts/dao/Auction.sol +++ b/protocol/contracts/dao/Auction.sol @@ -32,10 +32,10 @@ contract Auction is Comptroller { Epoch.CouponBidderState[] private bids; uint256 private totalFilled = 0; - uint256 private minExpiryFilled = 10000000000000000000; + uint256 private minExpiryFilled = 2**256 - 1; uint256 private maxExpiryFilled = 0; uint256 private sumExpiryFilled = 0; - Decimal.D256 private minYieldFilled = Decimal.D256(10000000000000000000); + Decimal.D256 private minYieldFilled = Decimal.D256(2**256 - 1); Decimal.D256 private maxYieldFilled = Decimal.zero(); Decimal.D256 private sumYieldFilled = Decimal.zero(); diff --git a/protocol/contracts/dao/Setters.sol b/protocol/contracts/dao/Setters.sol index 2b95f0f4..255cae78 100644 --- a/protocol/contracts/dao/Setters.sol +++ b/protocol/contracts/dao/Setters.sol @@ -153,11 +153,11 @@ contract Setters is State, Getters { if (_state.epochs[epoch()].auction.couponAuction == address(0)) { _state.epochs[epoch()].auction.couponAuction = auction; _state.epochs[epoch()].auction._totalBids = 0; - _state.epochs[epoch()].auction.minExpiry = 1000000000000000000000000; + _state.epochs[epoch()].auction.minExpiry = 2**256 - 1; _state.epochs[epoch()].auction.maxExpiry = 0; - _state.epochs[epoch()].auction.minYield = 1000000000000000000000000; + _state.epochs[epoch()].auction.minYield = 2**256 - 1; _state.epochs[epoch()].auction.maxYield = 0; - _state.epochs[epoch()].auction.minDollarAmount = 1000000000000000000000000; + _state.epochs[epoch()].auction.minDollarAmount = 2**256 - 1; _state.epochs[epoch()].auction.maxDollarAmount = 0; } } From cd29d39c8e53ee8f580bedb462e7e6018f1ef3bb Mon Sep 17 00:00:00 2001 From: cinquemb <1519017+cinquemb@users.noreply.github.com> Date: Thu, 7 Jan 2021 23:31:45 -0500 Subject: [PATCH 28/47] fixing coupon auction internals --- protocol/contracts/dao/Auction.sol | 17 +++++++++-------- protocol/test/dao/Auction.test.js | 11 ++++------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/protocol/contracts/dao/Auction.sol b/protocol/contracts/dao/Auction.sol index 32ba8da6..715e6ff2 100644 --- a/protocol/contracts/dao/Auction.sol +++ b/protocol/contracts/dao/Auction.sol @@ -32,12 +32,13 @@ contract Auction is Comptroller { Epoch.CouponBidderState[] private bids; uint256 private totalFilled = 0; - uint256 private minExpiryFilled = 2**256 - 1; uint256 private maxExpiryFilled = 0; uint256 private sumExpiryFilled = 0; - Decimal.D256 private minYieldFilled = Decimal.D256(2**256 - 1); + uint256 private sumYieldFilled = 0; + uint256 private minExpiryFilled = 2**256 - 1; Decimal.D256 private maxYieldFilled = Decimal.zero(); - Decimal.D256 private sumYieldFilled = Decimal.zero(); + Decimal.D256 private minYieldFilled = Decimal.D256(2**256 - 1); + function sortBidsByDistance(Epoch.CouponBidderState[] storage bids) internal returns(Epoch.CouponBidderState[] storage) { quickSort(bids, int(0), int(bids.length - 1)); @@ -144,8 +145,8 @@ contract Auction is Comptroller { maxExpiryFilled = bids[i].couponExpiryEpoch; } - sumYieldFilled.add(yield); - sumExpiryFilled.add(bids[i].couponExpiryEpoch); + sumYieldFilled += yield.asUint256(); + sumExpiryFilled += bids[i].couponExpiryEpoch; uint256 epoch = epoch().add(bids[i].couponExpiryEpoch); burnFromAccount(bids[i].bidder, bids[i].dollarAmount); @@ -161,8 +162,9 @@ contract Auction is Comptroller { // set auction internals if (totalFilled > 0) { + //.mul(100) to avoid sub 0 results Decimal.D256 memory avgYieldFilled = Decimal.ratio( - sumYieldFilled.asUint256(), + sumYieldFilled, totalFilled ); Decimal.D256 memory avgExpiryFilled = Decimal.ratio( @@ -172,7 +174,7 @@ contract Auction is Comptroller { Decimal.D256 memory bidToCover = Decimal.ratio( bids.length, totalFilled - ); + ).mul(100); setMinExpiryFilled(minExpiryFilled); setMaxExpiryFilled(maxExpiryFilled); @@ -183,7 +185,6 @@ contract Auction is Comptroller { setBidToCover(bidToCover.asUint256()); setTotalFilled(totalFilled); } - return true; } else { diff --git a/protocol/test/dao/Auction.test.js b/protocol/test/dao/Auction.test.js index 0f8ff5e0..5afcb134 100644 --- a/protocol/test/dao/Auction.test.js +++ b/protocol/test/dao/Auction.test.js @@ -54,14 +54,11 @@ describe('Auction', function () { expect(await this.auction.getMinExpiryFilled(1)).to.be.bignumber.equal(new BN(6)); expect(await this.auction.getMaxExpiryFilled(1)).to.be.bignumber.equal(new BN(1001)); - //TODO getAvgYieldFilled - expect(await this.auction.getAvgExpiryFilled(1)).to.be.bignumber.equal(new BN(0)); - expect(await this.auction.getMinYieldFilled(1)).to.be.bignumber.equal(new BN(10)); + expect(await this.auction.getAvgExpiryFilled(1)).to.be.bignumber.equal(new BN(342)); + expect(await this.auction.getMinYieldFilled(1)).to.be.bignumber.equal(new BN(25)); expect(await this.auction.getMaxYieldFilled(1)).to.be.bignumber.equal(new BN(55)); - //TODO getAvgYieldFilled - expect(await this.auction.getAvgYieldFilled(1)).to.be.bignumber.equal(new BN(0)); - //TODO getBidToCover - expect(await this.auction.getBidToCover(1)).to.be.bignumber.equal(new BN(1)); + expect(await this.auction.getAvgYieldFilled(1)).to.be.bignumber.equal(new BN(43)); + expect(await this.auction.getBidToCover(1)).to.be.bignumber.equal(new BN(133)); expect(await this.auction.getTotalFilled(1)).to.be.bignumber.equal(new BN(3)); }); }); From 8b47444dbe31cff65813c61158ce9bd1eabe5f14 Mon Sep 17 00:00:00 2001 From: cinquemb <1519017+cinquemb@users.noreply.github.com> Date: Fri, 8 Jan 2021 04:08:37 -0500 Subject: [PATCH 29/47] used auction factory to avoid high gas for regulator contract --- protocol/contracts/dao/Auction.sol | 7 ++++- protocol/contracts/dao/Regulator.sol | 4 ++- protocol/contracts/mock/MockState.sol | 4 +++ protocol/test/dao/Regulator.test.js | 44 +++++++++++++++++++++++++++ 4 files changed, 57 insertions(+), 2 deletions(-) diff --git a/protocol/contracts/dao/Auction.sol b/protocol/contracts/dao/Auction.sol index 715e6ff2..fcb01f95 100644 --- a/protocol/contracts/dao/Auction.sol +++ b/protocol/contracts/dao/Auction.sol @@ -19,10 +19,15 @@ pragma experimental ABIEncoderV2; import "@openzeppelin/contracts/math/SafeMath.sol"; import "./Comptroller.sol"; -import "./Market.sol"; import "../Constants.sol"; import "../external/Decimal.sol"; +contract AuctionFactory { + function createAuction() external returns (Auction) { + return new Auction(); + } +} + contract Auction is Comptroller { using SafeMath for uint256; using Decimal for Decimal.D256; diff --git a/protocol/contracts/dao/Regulator.sol b/protocol/contracts/dao/Regulator.sol index ac0caba3..0ec83f41 100644 --- a/protocol/contracts/dao/Regulator.sol +++ b/protocol/contracts/dao/Regulator.sol @@ -27,6 +27,8 @@ contract Regulator is Comptroller, Auction { using SafeMath for uint256; using Decimal for Decimal.D256; + AuctionFactory private auctionFactory; + event SupplyIncrease(uint256 indexed epoch, uint256 price, uint256 newRedeemable, uint256 lessDebt, uint256 newBonded); event SupplyDecrease(uint256 indexed epoch, uint256 price, uint256 newDebt); event SupplyNeutral(uint256 indexed epoch); @@ -53,7 +55,7 @@ contract Regulator is Comptroller, Auction { bool isAuctionSettled = settleCouponAuction(); finishCouponAuctionAtEpoch(epoch() - 1); } - Auction newCouponAuction = new Auction(); + Auction newCouponAuction = auctionFactory.createAuction(); initCouponAuction(address(newCouponAuction)); shrinkSupply(price); diff --git a/protocol/contracts/mock/MockState.sol b/protocol/contracts/mock/MockState.sol index 89af18bd..6173b634 100644 --- a/protocol/contracts/mock/MockState.sol +++ b/protocol/contracts/mock/MockState.sol @@ -119,6 +119,10 @@ contract MockState is Setters { super.eliminateOutstandingCoupons(epoch); } + function getCouponAuctionAddressAtEpochE(uint256 epoch) external returns (address) { + return super.getCouponAuctionAtEpoch(epoch).couponAuction; + } + /** * Governance diff --git a/protocol/test/dao/Regulator.test.js b/protocol/test/dao/Regulator.test.js index 670cf683..0ccf36de 100644 --- a/protocol/test/dao/Regulator.test.js +++ b/protocol/test/dao/Regulator.test.js @@ -75,6 +75,10 @@ describe('Regulator', function () { expect(await this.regulator.totalRedeemable()).to.be.bignumber.equal(new BN(0)); }); + it('has not created any auctions in the past 2 epochs', async function () { + expect(await this.regulator.getCouponAuctionAddressAtEpochE.call(1)).to.be.bignumber.equal(new BN(0)); + }); + it('emits SupplyIncrease event', async function () { const event = await expectEvent.inTransaction(this.txHash, MockRegulator, 'SupplyIncrease', {}); @@ -121,6 +125,10 @@ describe('Regulator', function () { expect(await this.regulator.totalRedeemable()).to.be.bignumber.equal(new BN(0)); }); + it('has not created any auctions in the past 2 epochs', async function () { + + }); + it('emits SupplyIncrease event', async function () { const event = await expectEvent.inTransaction(this.txHash, MockRegulator, 'SupplyIncrease', {}); @@ -175,6 +183,10 @@ describe('Regulator', function () { expect(await this.regulator.totalRedeemable()).to.be.bignumber.equal(new BN(this.expectedRewardCoupons)); }); + it('has not created any auctions in the past 2 epochs', async function () { + + }); + it('emits SupplyIncrease event', async function () { const event = await expectEvent.inTransaction(this.txHash, MockRegulator, 'SupplyIncrease', {}); @@ -230,6 +242,10 @@ describe('Regulator', function () { expect(await this.regulator.totalRedeemable()).to.be.bignumber.equal(new BN(2000)); }); + it('has not created any auctions in the past 2 epochs', async function () { + + }); + it('emits SupplyIncrease event', async function () { const event = await expectEvent.inTransaction(this.txHash, MockRegulator, 'SupplyIncrease', {}); @@ -284,6 +300,10 @@ describe('Regulator', function () { expect(await this.regulator.totalRedeemable()).to.be.bignumber.equal(new BN(this.expectedRewardCoupons)); }); + it('has not created any auctions in the past 2 epochs', async function () { + + }); + it('emits SupplyIncrease event', async function () { const event = await expectEvent.inTransaction(this.txHash, MockRegulator, 'SupplyIncrease', {}); @@ -323,6 +343,10 @@ describe('Regulator', function () { expect(await this.dollar.balanceOf(poolAddress)).to.be.bignumber.equal(new BN(0)); }); + it('has created 1 auction in the past 3 epochs', async function () { + + }); + it('updates totals', async function () { expect(await this.regulator.totalStaged()).to.be.bignumber.equal(new BN(0)); expect(await this.regulator.totalBonded()).to.be.bignumber.equal(new BN(1000000)); @@ -377,6 +401,10 @@ describe('Regulator', function () { expect(await this.regulator.totalRedeemable()).to.be.bignumber.equal(new BN(0)); }); + it('has created 1 auction in the past 2 epochs', async function () { + + }); + it('emits SupplyDecrease event', async function () { const event = await expectEvent.inTransaction(this.txHash, MockRegulator, 'SupplyDecrease', {}); @@ -425,6 +453,10 @@ describe('Regulator', function () { }); }); + it('has created 1 auction in the past 2 epochs', async function () { + + }); + it('emits SupplyDecrease event', async function () { const event = await expectEvent.inTransaction(this.txHash, MockRegulator, 'SupplyDecrease', {}); @@ -473,6 +505,10 @@ describe('Regulator', function () { }); }); + it('has created 1 auction in the past 2 epochs', async function () { + + }); + it('emits SupplyDecrease event', async function () { const event = await expectEvent.inTransaction(this.txHash, MockRegulator, 'SupplyDecrease', {}); @@ -521,6 +557,10 @@ describe('Regulator', function () { }); }); + it('has created 1 auction in the past 2 epochs', async function () { + + }); + it('emits SupplyDecrease event', async function () { const event = await expectEvent.inTransaction(this.txHash, MockRegulator, 'SupplyDecrease', {}); @@ -569,6 +609,10 @@ describe('Regulator', function () { }); }); + it('has created 1 auction in the past 2 epochs', async function () { + + }); + it('emits SupplyDecrease event', async function () { const event = await expectEvent.inTransaction(this.txHash, MockRegulator, 'SupplyDecrease', {}); From 9d667b41de054f6f4648097a6ab512b37bb8b185 Mon Sep 17 00:00:00 2001 From: cinquemb <1519017+cinquemb@users.noreply.github.com> Date: Fri, 8 Jan 2021 09:50:27 -0500 Subject: [PATCH 30/47] merged auction settlement stuff into regulator to bypass gas issues, started tests for auction stuff from regulator tests --- protocol/contracts/dao/Auction.sol | 6 - protocol/contracts/dao/Getters.sol | 2 +- protocol/contracts/dao/Regulator.sol | 182 +++++++++++++++++++++- protocol/contracts/dao/Setters.sol | 8 +- protocol/contracts/dao/State.sol | 2 +- protocol/contracts/mock/MockAuction.sol | 4 +- protocol/contracts/mock/MockRegulator.sol | 44 ++++++ protocol/contracts/mock/MockState.sol | 4 +- protocol/test/dao/Auction.test.js | 2 +- protocol/test/dao/Regulator.test.js | 74 ++++++--- 10 files changed, 280 insertions(+), 48 deletions(-) diff --git a/protocol/contracts/dao/Auction.sol b/protocol/contracts/dao/Auction.sol index fcb01f95..0790ffcb 100644 --- a/protocol/contracts/dao/Auction.sol +++ b/protocol/contracts/dao/Auction.sol @@ -22,12 +22,6 @@ import "./Comptroller.sol"; import "../Constants.sol"; import "../external/Decimal.sol"; -contract AuctionFactory { - function createAuction() external returns (Auction) { - return new Auction(); - } -} - contract Auction is Comptroller { using SafeMath for uint256; using Decimal for Decimal.D256; diff --git a/protocol/contracts/dao/Getters.sol b/protocol/contracts/dao/Getters.sol index f9e75c4e..c67f60d1 100644 --- a/protocol/contracts/dao/Getters.sol +++ b/protocol/contracts/dao/Getters.sol @@ -194,7 +194,7 @@ contract Getters is State { function getCouponAuctionAtEpoch(uint256 epoch) internal view returns (Epoch.AuctionState storage) { return _state.epochs[epoch].auction; } - + function getCouponAuctionBids() internal view returns (uint256) { return _state.epochs[epoch()].auction._totalBids; } diff --git a/protocol/contracts/dao/Regulator.sol b/protocol/contracts/dao/Regulator.sol index 0ec83f41..e3ef765c 100644 --- a/protocol/contracts/dao/Regulator.sol +++ b/protocol/contracts/dao/Regulator.sol @@ -19,15 +19,22 @@ pragma experimental ABIEncoderV2; import "@openzeppelin/contracts/math/SafeMath.sol"; import "./Comptroller.sol"; -import "./Auction.sol"; import "../external/Decimal.sol"; import "../Constants.sol"; -contract Regulator is Comptroller, Auction { +contract Regulator is Comptroller { using SafeMath for uint256; using Decimal for Decimal.D256; - AuctionFactory private auctionFactory; + bytes32 private constant FILE = "Regulator"; + Epoch.CouponBidderState[] private bids; + uint256 private totalFilled = 0; + uint256 private maxExpiryFilled = 0; + uint256 private sumExpiryFilled = 0; + uint256 private sumYieldFilled = 0; + uint256 private minExpiryFilled = 2**256 - 1; + Decimal.D256 private maxYieldFilled = Decimal.zero(); + Decimal.D256 private minYieldFilled = Decimal.D256(2**256 - 1); event SupplyIncrease(uint256 indexed epoch, uint256 price, uint256 newRedeemable, uint256 lessDebt, uint256 newBonded); event SupplyDecrease(uint256 indexed epoch, uint256 price, uint256 newDebt); @@ -41,7 +48,7 @@ contract Regulator is Comptroller, Auction { if (price.greaterThan(Decimal.one())) { //check for outstanding auction, if exists cancel it - if (auction.couponAuction != address(0)){ + if (auction.isInit == true){ cancelCouponAuctionAtEpoch(epoch() - 1); } @@ -51,12 +58,11 @@ contract Regulator is Comptroller, Auction { if (price.lessThan(Decimal.one())) { //check for outstanding auction, if exists settle it and start a new one - if (auction.couponAuction != address(0)){ + if (auction.isInit == true){ bool isAuctionSettled = settleCouponAuction(); finishCouponAuctionAtEpoch(epoch() - 1); } - Auction newCouponAuction = auctionFactory.createAuction(); - initCouponAuction(address(newCouponAuction)); + initCouponAuction(); shrinkSupply(price); return; @@ -108,5 +114,167 @@ contract Regulator is Comptroller, Auction { } return price; + } + + function sortBidsByDistance(Epoch.CouponBidderState[] storage bids) internal returns(Epoch.CouponBidderState[] storage) { + quickSort(bids, int(0), int(bids.length - 1)); + return bids; + } + + function quickSort(Epoch.CouponBidderState[] memory arr, int left, int right) internal { + int i = left; + int j = right; + if(i==j) return; + Decimal.D256 memory pivot = arr[uint256(left + (right - left) / 2)].distance; + while (i <= j) { + while (arr[uint256(i)].distance.lessThan(pivot)) i++; + while (pivot.lessThan(arr[uint256(j)].distance)) j--; + if (i <= j) { + (arr[uint256(i)], arr[uint256(j)]) = (arr[uint256(j)], arr[uint256(i)]); + i++; + j--; + } + } + if (left < j) + quickSort(arr, left, j); + if (i < right) + quickSort(arr, i, right); + } + + function sqrt(Decimal.D256 memory x) internal pure returns (Decimal.D256 memory y) { + Decimal.D256 memory z = x.add(1).div(2); + y = x; + while (z.lessThan(y)) { + y = z; + z = x.div(z.add(z)).div(2); + } + return y; + } + + function settleCouponAuction() internal returns (bool success) { + if (!isCouponAuctionFinished() && !isCouponAuctionCanceled()) { + uint256 yieldRelNorm = getCouponAuctionMaxYield() - getCouponAuctionMinYield(); + uint256 expiryRelNorm = getCouponAuctionMaxExpiry() - getCouponAuctionMinExpiry(); + uint256 dollarRelNorm = getCouponAuctionMaxDollarAmount() - getCouponAuctionMinDollarAmount(); + + // loop over bids and compute distance + for (uint256 i = 0; i < getCouponAuctionBids(); i++) { + Epoch.CouponBidderState storage bidder = getCouponBidderState(getCouponBidderStateIndex(i)); + Decimal.D256 memory yieldRel = Decimal.ratio( + Decimal.ratio( + bidder.couponAmount, + bidder.dollarAmount + ).asUint256(), + yieldRelNorm + ); + + Decimal.D256 memory expiryRel = Decimal.ratio( + bidder.couponExpiryEpoch, + expiryRelNorm + ); + + Decimal.D256 memory dollarRelMax = Decimal.ratio( + bidder.dollarAmount, + dollarRelNorm + ); + Decimal.D256 memory dollarRel = (Decimal.one().add(Decimal.one())).sub(dollarRelMax); + + Decimal.D256 memory yieldRelSquared = yieldRel.pow(2); + Decimal.D256 memory expiryRelSquared = expiryRel.pow(2); + Decimal.D256 memory dollarRelSquared = dollarRel.pow(2); + + Decimal.D256 memory sumOfSquared = yieldRelSquared.add(expiryRelSquared).add(dollarRelSquared); + Decimal.D256 memory distance; + if (sumOfSquared.greaterThan(Decimal.zero())) { + distance = sqrt(sumOfSquared); + } else { + distance = Decimal.zero(); + } + + setCouponBidderStateDistance(getCouponBidderStateIndex(i), distance); + bidder = getCouponBidderState(getCouponBidderStateIndex(i)); + bids.push(bidder); + } + + + // sort bids + bids = sortBidsByDistance(bids); + + // assign coupons until totalDebt filled, reject the rest + for (uint256 i = 0; i < bids.length; i++) { + if (totalDebt() >= bids[i].dollarAmount) { + if (!getCouponBidderStateRejected(bids[i].bidder) && !getCouponBidderStateRejected(bids[i].bidder)) { + Decimal.D256 memory yield = Decimal.ratio( + bids[i].couponAmount, + bids[i].dollarAmount + ); + + if (yield.lessThan(minYieldFilled)) { + minYieldFilled = yield; + } else if (yield.greaterThan(maxYieldFilled)) { + maxYieldFilled = yield; + } + + if (bids[i].couponExpiryEpoch < minExpiryFilled) { + minExpiryFilled = bids[i].couponExpiryEpoch; + } else if (bids[i].couponExpiryEpoch > maxExpiryFilled) { + maxExpiryFilled = bids[i].couponExpiryEpoch; + } + + sumYieldFilled += yield.asUint256(); + sumExpiryFilled += bids[i].couponExpiryEpoch; + + uint256 epoch = epoch().add(bids[i].couponExpiryEpoch); + burnFromAccount(bids[i].bidder, bids[i].dollarAmount); + incrementBalanceOfCoupons(bids[i].bidder, epoch, bids[i].couponAmount); + setCouponBidderStateSelected(bids[i].bidder); + totalFilled++; + } + } else { + /* setCouponBidderStateRejected(bids[i].bidder); or just break and close the auction */ + break; + } + } + + // set auction internals + if (totalFilled > 0) { + //.mul(100) to avoid sub 0 results + Decimal.D256 memory avgYieldFilled = Decimal.ratio( + sumYieldFilled, + totalFilled + ); + Decimal.D256 memory avgExpiryFilled = Decimal.ratio( + sumExpiryFilled, + totalFilled + ); + Decimal.D256 memory bidToCover = Decimal.ratio( + bids.length, + totalFilled + ).mul(100); + + setMinExpiryFilled(minExpiryFilled); + setMaxExpiryFilled(maxExpiryFilled); + setAvgExpiryFilled(avgExpiryFilled.asUint256()); + setMinYieldFilled(minYieldFilled.asUint256()); + setMaxYieldFilled(maxYieldFilled.asUint256()); + setAvgYieldFilled(avgYieldFilled.asUint256()); + setBidToCover(bidToCover.asUint256()); + setTotalFilled(totalFilled); + } + + //clear bids and reset vars + delete bids; + totalFilled = 0; + maxExpiryFilled = 0; + sumExpiryFilled = 0; + sumYieldFilled = 0; + minExpiryFilled = 2**256 - 1; + maxYieldFilled = Decimal.zero(); + minYieldFilled = Decimal.D256(2**256 - 1); + + return true; + } else { + return false; + } } } diff --git a/protocol/contracts/dao/Setters.sol b/protocol/contracts/dao/Setters.sol index 255cae78..39936e7d 100644 --- a/protocol/contracts/dao/Setters.sol +++ b/protocol/contracts/dao/Setters.sol @@ -149,9 +149,8 @@ contract Setters is State, Getters { _state.epochs[epoch].coupons.outstanding = 0; } - function initCouponAuction(address auction) internal { - if (_state.epochs[epoch()].auction.couponAuction == address(0)) { - _state.epochs[epoch()].auction.couponAuction = auction; + function initCouponAuction() internal { + if (_state.epochs[epoch()].auction.isInit == false) { _state.epochs[epoch()].auction._totalBids = 0; _state.epochs[epoch()].auction.minExpiry = 2**256 - 1; _state.epochs[epoch()].auction.maxExpiry = 0; @@ -159,6 +158,7 @@ contract Setters is State, Getters { _state.epochs[epoch()].auction.maxYield = 0; _state.epochs[epoch()].auction.minDollarAmount = 2**256 - 1; _state.epochs[epoch()].auction.maxDollarAmount = 0; + _state.epochs[epoch()].auction.isInit = true; } } @@ -177,8 +177,6 @@ contract Setters is State, Getters { bidderState.dollarAmount = dollarAmount; bidderState.couponAmount = maxCouponAmount; bidderState.bidder = bidder; - - _state.epochs[epoch()].auction.couponBidderState[bidder] = bidderState; } function setCouponBidderStateDistance(address bidder, Decimal.D256 memory distance) internal { diff --git a/protocol/contracts/dao/State.sol b/protocol/contracts/dao/State.sol index 8aa7197d..d9013b5a 100644 --- a/protocol/contracts/dao/State.sol +++ b/protocol/contracts/dao/State.sol @@ -63,6 +63,7 @@ contract Epoch { } struct AuctionState { + bool isInit; bool canceled; bool finished; uint256 minExpiry; @@ -72,7 +73,6 @@ contract Epoch { uint256 _totalBids; uint256 totalFilled; uint256 bidToCover; - address couponAuction; uint256 minYieldFilled; uint256 maxYieldFilled; uint256 avgYieldFilled; diff --git a/protocol/contracts/mock/MockAuction.sol b/protocol/contracts/mock/MockAuction.sol index 7465a00a..7f9f5d19 100644 --- a/protocol/contracts/mock/MockAuction.sol +++ b/protocol/contracts/mock/MockAuction.sol @@ -43,8 +43,8 @@ contract MockAuction is MockState, MockComptroller, Auction, Market { super.finishCouponAuctionAtEpoch(epoch); } - function initCouponAuctionE(address auction) external { - super.initCouponAuction(auction); + function initCouponAuctionE() external { + super.initCouponAuction(); } function getCouponAuctionBidsE() external returns (uint256) { diff --git a/protocol/contracts/mock/MockRegulator.sol b/protocol/contracts/mock/MockRegulator.sol index d8787664..b19b735f 100644 --- a/protocol/contracts/mock/MockRegulator.sol +++ b/protocol/contracts/mock/MockRegulator.sol @@ -34,4 +34,48 @@ contract MockRegulator is MockComptroller, Regulator { function bootstrappingAt(uint256 epoch) public view returns (bool) { return epoch <= 5; } + + function settleCouponAuctionE() external { + super.settleCouponAuction(); + } + + function cancelCouponAuctionAtEpochE(uint256 epoch) external { + super.cancelCouponAuctionAtEpoch(epoch); + } + + function finishCouponAuctionAtEpochE(uint256 epoch) external { + super.finishCouponAuctionAtEpoch(epoch); + } + + function initCouponAuctionE() external { + super.initCouponAuction(); + } + + function getCouponAuctionBidsE() external returns (uint256) { + return super.getCouponAuctionBids(); + } + + function getCouponAuctionMinExpiryE() external returns (uint256) { + return super.getCouponAuctionMinExpiry(); + } + + function getCouponAuctionMaxExpiryE() external returns (uint256) { + return super.getCouponAuctionMaxExpiry(); + } + + function getCouponAuctionMinYieldE() external returns (uint256) { + return super.getCouponAuctionMinYield(); + } + + function getCouponAuctionMaxYieldE() external returns (uint256) { + return super.getCouponAuctionMaxYield(); + } + + function getCouponAuctionMinDollarAmountE() external returns (uint256) { + return super.getCouponAuctionMinDollarAmount(); + } + + function getCouponAuctionMaxDollarAmountE() external returns (uint256) { + return super.getCouponAuctionMaxDollarAmount(); + } } diff --git a/protocol/contracts/mock/MockState.sol b/protocol/contracts/mock/MockState.sol index 6173b634..b73c8097 100644 --- a/protocol/contracts/mock/MockState.sol +++ b/protocol/contracts/mock/MockState.sol @@ -119,8 +119,8 @@ contract MockState is Setters { super.eliminateOutstandingCoupons(epoch); } - function getCouponAuctionAddressAtEpochE(uint256 epoch) external returns (address) { - return super.getCouponAuctionAtEpoch(epoch).couponAuction; + function isCouponAuctionInitAtEpochE(uint256 epoch) external returns (bool) { + return super.getCouponAuctionAtEpoch(epoch).isInit; } diff --git a/protocol/test/dao/Auction.test.js b/protocol/test/dao/Auction.test.js index 5afcb134..dad37a3e 100644 --- a/protocol/test/dao/Auction.test.js +++ b/protocol/test/dao/Auction.test.js @@ -30,7 +30,7 @@ describe('Auction', function () { describe('auction is not finished and not canceled', function () { beforeEach(async function () { await this.auction.incrementTotalDebtE(4000); - await this.auction.initCouponAuctionE(this.auction.address); + await this.auction.initCouponAuctionE(); }); diff --git a/protocol/test/dao/Regulator.test.js b/protocol/test/dao/Regulator.test.js index 0ccf36de..f97ffdde 100644 --- a/protocol/test/dao/Regulator.test.js +++ b/protocol/test/dao/Regulator.test.js @@ -75,8 +75,10 @@ describe('Regulator', function () { expect(await this.regulator.totalRedeemable()).to.be.bignumber.equal(new BN(0)); }); - it('has not created any auctions in the past 2 epochs', async function () { - expect(await this.regulator.getCouponAuctionAddressAtEpochE.call(1)).to.be.bignumber.equal(new BN(0)); + it('has not created any auction in the past 7 epochs', async function () { + for(var a_idx = 1; a_idx<8; a_idx++){ + expect(await this.regulator.isCouponAuctionInitAtEpochE.call(a_idx)).equal(false); + } }); it('emits SupplyIncrease event', async function () { @@ -125,8 +127,10 @@ describe('Regulator', function () { expect(await this.regulator.totalRedeemable()).to.be.bignumber.equal(new BN(0)); }); - it('has not created any auctions in the past 2 epochs', async function () { - + it('has not created any auction in the past 7 epochs', async function () { + for(var a_idx = 1; a_idx<8; a_idx++){ + expect(await this.regulator.isCouponAuctionInitAtEpochE.call(a_idx)).equal(false); + } }); it('emits SupplyIncrease event', async function () { @@ -183,8 +187,10 @@ describe('Regulator', function () { expect(await this.regulator.totalRedeemable()).to.be.bignumber.equal(new BN(this.expectedRewardCoupons)); }); - it('has not created any auctions in the past 2 epochs', async function () { - + it('has not created any auction in the past 7 epochs', async function () { + for(var a_idx = 1; a_idx<8; a_idx++){ + expect(await this.regulator.isCouponAuctionInitAtEpochE.call(a_idx)).equal(false); + } }); it('emits SupplyIncrease event', async function () { @@ -242,9 +248,11 @@ describe('Regulator', function () { expect(await this.regulator.totalRedeemable()).to.be.bignumber.equal(new BN(2000)); }); - it('has not created any auctions in the past 2 epochs', async function () { - - }); + it('has not created any auction in the past 7 epochs', async function () { + for(var a_idx = 1; a_idx<8; a_idx++){ + expect(await this.regulator.isCouponAuctionInitAtEpochE.call(a_idx)).equal(false); + } + }); it('emits SupplyIncrease event', async function () { const event = await expectEvent.inTransaction(this.txHash, MockRegulator, 'SupplyIncrease', {}); @@ -300,8 +308,10 @@ describe('Regulator', function () { expect(await this.regulator.totalRedeemable()).to.be.bignumber.equal(new BN(this.expectedRewardCoupons)); }); - it('has not created any auctions in the past 2 epochs', async function () { - + it('has not created any auction in the past 7 epochs', async function () { + for(var a_idx = 1; a_idx<8; a_idx++){ + expect(await this.regulator.isCouponAuctionInitAtEpochE.call(a_idx)).equal(false); + } }); it('emits SupplyIncrease event', async function () { @@ -343,8 +353,11 @@ describe('Regulator', function () { expect(await this.dollar.balanceOf(poolAddress)).to.be.bignumber.equal(new BN(0)); }); - it('has created 1 auction in the past 3 epochs', async function () { - + it('has created 1 auction in the past 8 epochs', async function () { + for(var a_idx = 1; a_idx<8; a_idx++){ + expect(await this.regulator.isCouponAuctionInitAtEpochE.call(a_idx)).equal(false); + } + expect(await this.regulator.isCouponAuctionInitAtEpochE.call(8)).equal(true); }); it('updates totals', async function () { @@ -401,8 +414,11 @@ describe('Regulator', function () { expect(await this.regulator.totalRedeemable()).to.be.bignumber.equal(new BN(0)); }); - it('has created 1 auction in the past 2 epochs', async function () { - + it('has created 1 auction in the past 7 epochs', async function () { + for(var a_idx = 1; a_idx<7; a_idx++){ + expect(await this.regulator.isCouponAuctionInitAtEpochE.call(a_idx)).equal(false); + } + expect(await this.regulator.isCouponAuctionInitAtEpochE.call(7)).equal(true); }); it('emits SupplyDecrease event', async function () { @@ -453,8 +469,11 @@ describe('Regulator', function () { }); }); - it('has created 1 auction in the past 2 epochs', async function () { - + it('has created 1 auction in the past 7 epochs', async function () { + for(var a_idx = 1; a_idx<7; a_idx++){ + expect(await this.regulator.isCouponAuctionInitAtEpochE.call(a_idx)).equal(false); + } + expect(await this.regulator.isCouponAuctionInitAtEpochE.call(7)).equal(true); }); it('emits SupplyDecrease event', async function () { @@ -505,8 +524,11 @@ describe('Regulator', function () { }); }); - it('has created 1 auction in the past 2 epochs', async function () { - + it('has created 1 auction in the past 7 epochs', async function () { + for(var a_idx = 1; a_idx<7; a_idx++){ + expect(await this.regulator.isCouponAuctionInitAtEpochE.call(a_idx)).equal(false); + } + expect(await this.regulator.isCouponAuctionInitAtEpochE.call(7)).equal(true); }); it('emits SupplyDecrease event', async function () { @@ -557,8 +579,11 @@ describe('Regulator', function () { }); }); - it('has created 1 auction in the past 2 epochs', async function () { - + it('has created 1 auction in the past 7 epochs', async function () { + for(var a_idx = 1; a_idx<7; a_idx++){ + expect(await this.regulator.isCouponAuctionInitAtEpochE.call(a_idx)).equal(false); + } + expect(await this.regulator.isCouponAuctionInitAtEpochE.call(7)).equal(true); }); it('emits SupplyDecrease event', async function () { @@ -609,8 +634,11 @@ describe('Regulator', function () { }); }); - it('has created 1 auction in the past 2 epochs', async function () { - + it('has created 1 auction in the past 7 epochs', async function () { + for(var a_idx = 1; a_idx<7; a_idx++){ + expect(await this.regulator.isCouponAuctionInitAtEpochE.call(a_idx)).equal(false); + } + expect(await this.regulator.isCouponAuctionInitAtEpochE.call(7)).equal(true); }); it('emits SupplyDecrease event', async function () { From ac9628e7fb19e3286d61f683688b48a1638c4f26 Mon Sep 17 00:00:00 2001 From: cinquemb <1519017+cinquemb@users.noreply.github.com> Date: Fri, 8 Jan 2021 12:49:52 -0500 Subject: [PATCH 31/47] removing unneed files, more work on regulator tests --- protocol/contracts/dao/Auction.sol | 193 ---------------------- protocol/contracts/dao/Regulator.sol | 3 +- protocol/contracts/dao/Setters.sol | 6 +- protocol/contracts/mock/MockAuction.sol | 77 --------- protocol/contracts/mock/MockRegulator.sol | 7 +- protocol/test/dao/Auction.test.js | 102 ------------ protocol/test/dao/Regulator.test.js | 80 ++++++++- 7 files changed, 90 insertions(+), 378 deletions(-) delete mode 100644 protocol/contracts/dao/Auction.sol delete mode 100644 protocol/contracts/mock/MockAuction.sol delete mode 100644 protocol/test/dao/Auction.test.js diff --git a/protocol/contracts/dao/Auction.sol b/protocol/contracts/dao/Auction.sol deleted file mode 100644 index 0790ffcb..00000000 --- a/protocol/contracts/dao/Auction.sol +++ /dev/null @@ -1,193 +0,0 @@ -/* - Copyright 2020 Empty Set Squad - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -pragma solidity ^0.5.17; -pragma experimental ABIEncoderV2; - -import "@openzeppelin/contracts/math/SafeMath.sol"; -import "./Comptroller.sol"; -import "../Constants.sol"; -import "../external/Decimal.sol"; - -contract Auction is Comptroller { - using SafeMath for uint256; - using Decimal for Decimal.D256; - - bytes32 private constant FILE = "Auction"; - - Epoch.CouponBidderState[] private bids; - - uint256 private totalFilled = 0; - uint256 private maxExpiryFilled = 0; - uint256 private sumExpiryFilled = 0; - uint256 private sumYieldFilled = 0; - uint256 private minExpiryFilled = 2**256 - 1; - Decimal.D256 private maxYieldFilled = Decimal.zero(); - Decimal.D256 private minYieldFilled = Decimal.D256(2**256 - 1); - - - function sortBidsByDistance(Epoch.CouponBidderState[] storage bids) internal returns(Epoch.CouponBidderState[] storage) { - quickSort(bids, int(0), int(bids.length - 1)); - return bids; - } - - function quickSort(Epoch.CouponBidderState[] memory arr, int left, int right) internal { - int i = left; - int j = right; - if(i==j) return; - Decimal.D256 memory pivot = arr[uint256(left + (right - left) / 2)].distance; - while (i <= j) { - while (arr[uint256(i)].distance.lessThan(pivot)) i++; - while (pivot.lessThan(arr[uint256(j)].distance)) j--; - if (i <= j) { - (arr[uint256(i)], arr[uint256(j)]) = (arr[uint256(j)], arr[uint256(i)]); - i++; - j--; - } - } - if (left < j) - quickSort(arr, left, j); - if (i < right) - quickSort(arr, i, right); - } - - function sqrt(Decimal.D256 memory x) internal pure returns (Decimal.D256 memory y) { - Decimal.D256 memory z = x.add(1).div(2); - y = x; - while (z.lessThan(y)) { - y = z; - z = x.div(z.add(z)).div(2); - } - return y; - } - - function settleCouponAuction() internal returns (bool success) { - if (!isCouponAuctionFinished() && !isCouponAuctionCanceled()) { - uint256 yieldRelNorm = getCouponAuctionMaxYield() - getCouponAuctionMinYield(); - uint256 expiryRelNorm = getCouponAuctionMaxExpiry() - getCouponAuctionMinExpiry(); - uint256 dollarRelNorm = getCouponAuctionMaxDollarAmount() - getCouponAuctionMinDollarAmount(); - - // loop over bids and compute distance - for (uint256 i = 0; i < getCouponAuctionBids(); i++) { - Epoch.CouponBidderState storage bidder = getCouponBidderState(getCouponBidderStateIndex(i)); - Decimal.D256 memory yieldRel = Decimal.ratio( - Decimal.ratio( - bidder.couponAmount, - bidder.dollarAmount - ).asUint256(), - yieldRelNorm - ); - - Decimal.D256 memory expiryRel = Decimal.ratio( - bidder.couponExpiryEpoch, - expiryRelNorm - ); - - Decimal.D256 memory dollarRelMax = Decimal.ratio( - bidder.dollarAmount, - dollarRelNorm - ); - Decimal.D256 memory dollarRel = (Decimal.one().add(Decimal.one())).sub(dollarRelMax); - - Decimal.D256 memory yieldRelSquared = yieldRel.pow(2); - Decimal.D256 memory expiryRelSquared = expiryRel.pow(2); - Decimal.D256 memory dollarRelSquared = dollarRel.pow(2); - - Decimal.D256 memory sumOfSquared = yieldRelSquared.add(expiryRelSquared).add(dollarRelSquared); - Decimal.D256 memory distance; - if (sumOfSquared.greaterThan(Decimal.zero())) { - distance = sqrt(sumOfSquared); - } else { - distance = Decimal.zero(); - } - - setCouponBidderStateDistance(getCouponBidderStateIndex(i), distance); - bidder = getCouponBidderState(getCouponBidderStateIndex(i)); - bids.push(bidder); - } - - - // sort bids - bids = sortBidsByDistance(bids); - - // assign coupons until totalDebt filled, reject the rest - for (uint256 i = 0; i < bids.length; i++) { - if (totalDebt() >= bids[i].dollarAmount) { - if (!getCouponBidderStateRejected(bids[i].bidder) && !getCouponBidderStateRejected(bids[i].bidder)) { - Decimal.D256 memory yield = Decimal.ratio( - bids[i].couponAmount, - bids[i].dollarAmount - ); - - if (yield.lessThan(minYieldFilled)) { - minYieldFilled = yield; - } else if (yield.greaterThan(maxYieldFilled)) { - maxYieldFilled = yield; - } - - if (bids[i].couponExpiryEpoch < minExpiryFilled) { - minExpiryFilled = bids[i].couponExpiryEpoch; - } else if (bids[i].couponExpiryEpoch > maxExpiryFilled) { - maxExpiryFilled = bids[i].couponExpiryEpoch; - } - - sumYieldFilled += yield.asUint256(); - sumExpiryFilled += bids[i].couponExpiryEpoch; - - uint256 epoch = epoch().add(bids[i].couponExpiryEpoch); - burnFromAccount(bids[i].bidder, bids[i].dollarAmount); - incrementBalanceOfCoupons(bids[i].bidder, epoch, bids[i].couponAmount); - setCouponBidderStateSelected(bids[i].bidder); - totalFilled++; - } - } else { - /* setCouponBidderStateRejected(bids[i].bidder); or just break and close the auction */ - break; - } - } - - // set auction internals - if (totalFilled > 0) { - //.mul(100) to avoid sub 0 results - Decimal.D256 memory avgYieldFilled = Decimal.ratio( - sumYieldFilled, - totalFilled - ); - Decimal.D256 memory avgExpiryFilled = Decimal.ratio( - sumExpiryFilled, - totalFilled - ); - Decimal.D256 memory bidToCover = Decimal.ratio( - bids.length, - totalFilled - ).mul(100); - - setMinExpiryFilled(minExpiryFilled); - setMaxExpiryFilled(maxExpiryFilled); - setAvgExpiryFilled(avgExpiryFilled.asUint256()); - setMinYieldFilled(minYieldFilled.asUint256()); - setMaxYieldFilled(maxYieldFilled.asUint256()); - setAvgYieldFilled(avgYieldFilled.asUint256()); - setBidToCover(bidToCover.asUint256()); - setTotalFilled(totalFilled); - } - - return true; - } else { - return false; - } - } -} \ No newline at end of file diff --git a/protocol/contracts/dao/Regulator.sol b/protocol/contracts/dao/Regulator.sol index e3ef765c..a1e58822 100644 --- a/protocol/contracts/dao/Regulator.sol +++ b/protocol/contracts/dao/Regulator.sol @@ -238,7 +238,6 @@ contract Regulator is Comptroller { // set auction internals if (totalFilled > 0) { - //.mul(100) to avoid sub 0 results Decimal.D256 memory avgYieldFilled = Decimal.ratio( sumYieldFilled, totalFilled @@ -247,6 +246,8 @@ contract Regulator is Comptroller { sumExpiryFilled, totalFilled ); + + //mul(100) to avoid sub 0 results Decimal.D256 memory bidToCover = Decimal.ratio( bids.length, totalFilled diff --git a/protocol/contracts/dao/Setters.sol b/protocol/contracts/dao/Setters.sol index 39936e7d..b0baa932 100644 --- a/protocol/contracts/dao/Setters.sol +++ b/protocol/contracts/dao/Setters.sol @@ -152,11 +152,11 @@ contract Setters is State, Getters { function initCouponAuction() internal { if (_state.epochs[epoch()].auction.isInit == false) { _state.epochs[epoch()].auction._totalBids = 0; - _state.epochs[epoch()].auction.minExpiry = 2**256 - 1; + _state.epochs[epoch()].auction.minExpiry = 2**256 -1; _state.epochs[epoch()].auction.maxExpiry = 0; - _state.epochs[epoch()].auction.minYield = 2**256 - 1; + _state.epochs[epoch()].auction.minYield = 2**256 -1; _state.epochs[epoch()].auction.maxYield = 0; - _state.epochs[epoch()].auction.minDollarAmount = 2**256 - 1; + _state.epochs[epoch()].auction.minDollarAmount = 2**256 -1; _state.epochs[epoch()].auction.maxDollarAmount = 0; _state.epochs[epoch()].auction.isInit = true; } diff --git a/protocol/contracts/mock/MockAuction.sol b/protocol/contracts/mock/MockAuction.sol deleted file mode 100644 index 7f9f5d19..00000000 --- a/protocol/contracts/mock/MockAuction.sol +++ /dev/null @@ -1,77 +0,0 @@ -/* - Copyright 2020 Empty Set Squad - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -pragma solidity ^0.5.17; -pragma experimental ABIEncoderV2; - -import "../dao/Auction.sol"; -import "../dao/Market.sol"; -import "./MockState.sol"; -import "./MockMarket.sol"; -import "./MockComptroller.sol"; - - -contract MockAuction is MockState, MockComptroller, Auction, Market { - constructor(address pool) MockComptroller(pool) public { } - - function stepE() external { - Market.step(); - } - - function settleCouponAuctionE() external returns (bool) { - return settleCouponAuction(); - } - - function cancelCouponAuctionAtEpochE(uint256 epoch) external { - super.cancelCouponAuctionAtEpoch(epoch); - } - - function finishCouponAuctionAtEpochE(uint256 epoch) external { - super.finishCouponAuctionAtEpoch(epoch); - } - - function initCouponAuctionE() external { - super.initCouponAuction(); - } - - function getCouponAuctionBidsE() external returns (uint256) { - return super.getCouponAuctionBids(); - } - - function getCouponAuctionMinExpiryE() external returns (uint256) { - return super.getCouponAuctionMinExpiry(); - } - - function getCouponAuctionMaxExpiryE() external returns (uint256) { - return super.getCouponAuctionMaxExpiry(); - } - - function getCouponAuctionMinYieldE() external returns (uint256) { - return super.getCouponAuctionMinYield(); - } - - function getCouponAuctionMaxYieldE() external returns (uint256) { - return super.getCouponAuctionMaxYield(); - } - - function getCouponAuctionMinDollarAmountE() external returns (uint256) { - return super.getCouponAuctionMinDollarAmount(); - } - - function getCouponAuctionMaxDollarAmountE() external returns (uint256) { - return super.getCouponAuctionMaxDollarAmount(); - } -} diff --git a/protocol/contracts/mock/MockRegulator.sol b/protocol/contracts/mock/MockRegulator.sol index b19b735f..11ac0b2d 100644 --- a/protocol/contracts/mock/MockRegulator.sol +++ b/protocol/contracts/mock/MockRegulator.sol @@ -18,11 +18,12 @@ pragma solidity ^0.5.17; pragma experimental ABIEncoderV2; import "../dao/Regulator.sol"; +import "../dao/Market.sol"; import "../oracle/IOracle.sol"; import "./MockComptroller.sol"; import "./MockState.sol"; -contract MockRegulator is MockComptroller, Regulator { +contract MockRegulator is MockComptroller, Regulator, Market { constructor (address oracle, address pool) MockComptroller(pool) public { _state.provider.oracle = IOracle(oracle); } @@ -35,6 +36,10 @@ contract MockRegulator is MockComptroller, Regulator { return epoch <= 5; } + /*function placeCouponAuctionBid(uint256 couponEpochExpiry, uint256 dollarAmount, uint256 maxCouponAmount) { + super.placeCouponAuctionBid(couponEpochExpiry, dollarAmount, maxCouponAmount); + }*/ + function settleCouponAuctionE() external { super.settleCouponAuction(); } diff --git a/protocol/test/dao/Auction.test.js b/protocol/test/dao/Auction.test.js deleted file mode 100644 index dad37a3e..00000000 --- a/protocol/test/dao/Auction.test.js +++ /dev/null @@ -1,102 +0,0 @@ -const { accounts, contract } = require('@openzeppelin/test-environment'); - -const { BN, expectRevert, time } = require('@openzeppelin/test-helpers'); -const { expect } = require('chai'); - -const MockAuction = contract.fromArtifact('MockAuction'); -const MockMarket = contract.fromArtifact('MockMarket'); -const Dollar = contract.fromArtifact('Dollar'); - -describe('Auction', function () { - const [ ownerAddress, poolAddress, userAddress, userAddress2, userAddress3, userAddress4 ] = accounts; - - beforeEach(async function () { - this.auction = await MockAuction.new(poolAddress, {from: ownerAddress, gas: 8000000}); - this.dollar = await Dollar.at(await this.auction.dollar()); - - await this.auction.incrementEpochE(); - await this.auction.stepE(); - await this.auction.mintToE(userAddress, 1000000); - await this.auction.mintToE(userAddress2, 1000000); - await this.auction.mintToE(userAddress3, 1000000); - await this.auction.mintToE(userAddress4, 1000000); - await this.dollar.approve(this.auction.address, 1000000, {from: userAddress}); - await this.dollar.approve(this.auction.address, 1000000, {from: userAddress2}); - await this.dollar.approve(this.auction.address, 1000000, {from: userAddress3}); - await this.dollar.approve(this.auction.address, 1000000, {from: userAddress4}); - }); - - describe('when settling auction', function () { - describe('auction is not finished and not canceled', function () { - beforeEach(async function () { - await this.auction.incrementTotalDebtE(4000); - await this.auction.initCouponAuctionE(); - }); - - - it('is able to settle auction and generated internals and deincrement debt', async function () { - // add some bidders - this.result = await this.auction.placeCouponAuctionBid(20, 1000, 50000, {from: userAddress}); - this.result1 = await this.auction.placeCouponAuctionBid(5, 2000, 50000, {from: userAddress2}); - //thise bidders will be rejected - this.result2 = await this.auction.placeCouponAuctionBid(1000, 900, 50000, {from: userAddress3}); - this.result3 = await this.auction.placeCouponAuctionBid(100990, 900, 50000, {from: userAddress4}); - this.auction_settlement = this.auction.settleCouponAuctionE(); - - - expect(await this.auction.getCouponAuctionBidsE.call()).to.be.bignumber.equal(new BN(4)); - expect(await this.auction.getCouponAuctionMinExpiryE.call()).to.be.bignumber.equal(new BN(6)); - expect(await this.auction.getCouponAuctionMaxExpiryE.call()).to.be.bignumber.equal(new BN(100991)); - expect(await this.auction.getCouponAuctionMinYieldE.call()).to.be.bignumber.equal(new BN(25)); - expect(await this.auction.getCouponAuctionMaxYieldE.call()).to.be.bignumber.equal(new BN(55)); - expect(await this.auction.getCouponAuctionMinDollarAmountE.call()).to.be.bignumber.equal(new BN(900)); - expect(await this.auction.getCouponAuctionMaxDollarAmountE.call()).to.be.bignumber.equal(new BN(2000)); - - expect(await this.auction.getMinExpiryFilled(1)).to.be.bignumber.equal(new BN(6)); - expect(await this.auction.getMaxExpiryFilled(1)).to.be.bignumber.equal(new BN(1001)); - expect(await this.auction.getAvgExpiryFilled(1)).to.be.bignumber.equal(new BN(342)); - expect(await this.auction.getMinYieldFilled(1)).to.be.bignumber.equal(new BN(25)); - expect(await this.auction.getMaxYieldFilled(1)).to.be.bignumber.equal(new BN(55)); - expect(await this.auction.getAvgYieldFilled(1)).to.be.bignumber.equal(new BN(43)); - expect(await this.auction.getBidToCover(1)).to.be.bignumber.equal(new BN(133)); - expect(await this.auction.getTotalFilled(1)).to.be.bignumber.equal(new BN(3)); - }); - }); - - describe('auction is finished', function () { - beforeEach(async function () { - //finish the auction - await this.auction.incrementTotalDebtE(4000); - await this.auction.initCouponAuctionE(this.auction.address); - await this.auction.finishCouponAuctionAtEpochE(1); - }); - - it('is able to not settle auction', async function () { - // add some bidders - this.result = await this.auction.placeCouponAuctionBid(20, 1000, 50000, {from: userAddress}); - this.result1 = await this.auction.placeCouponAuctionBid(5, 2000, 50000, {from: userAddress2}); - this.auction_settlement = await this.auction.settleCouponAuctionE.call(); - expect(this.auction_settlement).to.be.equal(false); - }); - - - }); - describe('auction is canceled', function () { - beforeEach(async function () { - //finish the auction - await this.auction.incrementTotalDebtE(4000); - await this.auction.initCouponAuctionE(this.auction.address); - await this.auction.cancelCouponAuctionAtEpochE(1); - }); - - it('is able to not settle auction', async function () { - // add some bidders - this.result = await this.auction.placeCouponAuctionBid(20, 1000, 50000, {from: userAddress}); - this.result1 = await this.auction.placeCouponAuctionBid(5, 2000, 50000, {from: userAddress2}); - this.auction_settlement = await this.auction.settleCouponAuctionE.call(); - expect(this.auction_settlement).to.be.equal(false); - }); - - }); - }); -}); \ No newline at end of file diff --git a/protocol/test/dao/Regulator.test.js b/protocol/test/dao/Regulator.test.js index f97ffdde..4e455db0 100644 --- a/protocol/test/dao/Regulator.test.js +++ b/protocol/test/dao/Regulator.test.js @@ -24,7 +24,7 @@ function treasuryIncentive(newAmount) { } describe('Regulator', function () { - const [ ownerAddress, userAddress, poolAddress ] = accounts; + const [ ownerAddress, userAddress, poolAddress, userAddress2, userAddress3, userAddress4 ] = accounts; beforeEach(async function () { this.oracle = await MockSettableOracle.new({from: ownerAddress, gas: 8000000}); @@ -617,6 +617,84 @@ describe('Regulator', function () { this.txHash = this.result.tx; }); + describe('when settling auction', function () { + describe('auction is not finished and not canceled', function () { + beforeEach(async function () { + await this.regulator.mintToE(userAddress, 1000000); + await this.regulator.mintToE(userAddress2, 1000000); + await this.regulator.mintToE(userAddress3, 1000000); + await this.regulator.mintToE(userAddress4, 1000000); + await this.dollar.approve(this.regulator.address, 1000000, {from: userAddress}); + await this.dollar.approve(this.regulator.address, 1000000, {from: userAddress2}); + await this.dollar.approve(this.regulator.address, 1000000, {from: userAddress3}); + await this.dollar.approve(this.regulator.address, 1000000, {from: userAddress4}); + }); + + it('is able to settle auction and generated internals and deincrement debt', async function () { + // add some bidders + this.result = await this.regulator.placeCouponAuctionBid(20, 1000, 50000, {from: userAddress}); + this.result1 = await this.regulator.placeCouponAuctionBid(5, 2000, 50000, {from: userAddress2}); + //thise bidders will be rejected + this.result2 = await this.regulator.placeCouponAuctionBid(1000, 900, 50000, {from: userAddress3}); + this.result3 = await this.regulator.placeCouponAuctionBid(100990, 900, 50000, {from: userAddress4}); + this.auction_settlement = await this.regulator.settleCouponAuctionE(); + + + expect(await this.regulator.getCouponAuctionBidsE.call()).to.be.bignumber.equal(new BN(4)); + //Need to fix + expect(await this.regulator.getCouponAuctionMinExpiryE.call()).to.be.bignumber.equal(new BN(0)); + expect(await this.regulator.getCouponAuctionMaxExpiryE.call()).to.be.bignumber.equal(new BN(100997)); + //Need to fix + expect(await this.regulator.getCouponAuctionMinYieldE.call()).to.be.bignumber.equal(new BN(0)); + expect(await this.regulator.getCouponAuctionMaxYieldE.call()).to.be.bignumber.equal(new BN(55)); + //Need to fix + expect(await this.regulator.getCouponAuctionMinDollarAmountE.call()).to.be.bignumber.equal(new BN(0)); + expect(await this.regulator.getCouponAuctionMaxDollarAmountE.call()).to.be.bignumber.equal(new BN(2000)); + + expect(await this.regulator.getMinExpiryFilled(7)).to.be.bignumber.equal(new BN(12)); + expect(await this.regulator.getMaxExpiryFilled(7)).to.be.bignumber.equal(new BN(100997)); + expect(await this.regulator.getAvgExpiryFilled(7)).to.be.bignumber.equal(new BN(25510)); + expect(await this.regulator.getMinYieldFilled(7)).to.be.bignumber.equal(new BN(25)); + expect(await this.regulator.getMaxYieldFilled(7)).to.be.bignumber.equal(new BN(55)); + expect(await this.regulator.getAvgYieldFilled(7)).to.be.bignumber.equal(new BN(46)); + expect(await this.regulator.getBidToCover(7)).to.be.bignumber.equal(new BN(100)); + expect(await this.regulator.getTotalFilled(7)).to.be.bignumber.equal(new BN(4)); + }); + }); + + describe('auction is finished', function () { + beforeEach(async function () { + //finish the auction + await this.regulator.finishCouponAuctionAtEpochE(1); + }); + + it('is able to not settle auction', async function () { + // add some bidders + this.result = await this.regulator.placeCouponAuctionBid(20, 1000, 50000, {from: userAddress}); + this.result1 = await this.regulator.placeCouponAuctionBid(5, 2000, 50000, {from: userAddress2}); + this.auction_settlement = await this.regulator.settleCouponAuctionE.call(); + expect(this.auction_settlement).to.be.equal(false); + }); + + + }); + describe('auction is canceled', function () { + beforeEach(async function () { + //finish the auction + await this.regulator.cancelCouponAuctionAtEpochE(7); + }); + + it('is able to not settle auction', async function () { + // add some bidders + this.result = await this.regulator.placeCouponAuctionBid(20, 1000, 50000, {from: userAddress}); + this.result1 = await this.regulator.placeCouponAuctionBid(5, 2000, 50000, {from: userAddress2}); + this.auction_settlement = await this.regulator.settleCouponAuctionE.call(); + expect(this.auction_settlement).to.be.equal(false); + }); + + }); + }); + it('doesnt mint new Dollar tokens', async function () { expect(await this.dollar.totalSupply()).to.be.bignumber.equal(new BN(1000000)); expect(await this.dollar.balanceOf(this.regulator.address)).to.be.bignumber.equal(new BN(1000000)); From 811fdae07f978269335bc84f3379fdf0fe2720c1 Mon Sep 17 00:00:00 2001 From: cinquemb <1519017+cinquemb@users.noreply.github.com> Date: Sat, 9 Jan 2021 01:17:23 -0500 Subject: [PATCH 32/47] update regulator tests --- protocol/contracts/dao/Setters.sol | 12 ++++-- protocol/contracts/mock/MockRegulator.sol | 4 -- protocol/test/dao/Regulator.test.js | 49 +++++++++++++++++++++-- protocol/test/dao/State.test.js | 2 - 4 files changed, 54 insertions(+), 13 deletions(-) diff --git a/protocol/contracts/dao/Setters.sol b/protocol/contracts/dao/Setters.sol index b0baa932..cf1ae1e6 100644 --- a/protocol/contracts/dao/Setters.sol +++ b/protocol/contracts/dao/Setters.sol @@ -202,7 +202,9 @@ contract Setters is State, Getters { function setCouponAuctionRelYield(uint256 yield) internal { if (yield > _state.epochs[epoch()].auction.maxYield) { _state.epochs[epoch()].auction.maxYield = yield; - } else if (yield < _state.epochs[epoch()].auction.minYield) { + } + + if (_state.epochs[epoch()].auction.minYield > yield) { _state.epochs[epoch()].auction.minYield = yield; } } @@ -210,7 +212,9 @@ contract Setters is State, Getters { function setCouponAuctionRelExpiry(uint256 couponEpochExpiry) internal { if (couponEpochExpiry > _state.epochs[epoch()].auction.maxExpiry) { _state.epochs[epoch()].auction.maxExpiry = couponEpochExpiry; - } else if (couponEpochExpiry < _state.epochs[epoch()].auction.minExpiry) { + } + + if (couponEpochExpiry < _state.epochs[epoch()].auction.minExpiry) { _state.epochs[epoch()].auction.minExpiry = couponEpochExpiry; } } @@ -218,7 +222,9 @@ contract Setters is State, Getters { function setCouponAuctionRelDollarAmount(uint256 couponDollarAmount) internal { if (couponDollarAmount > _state.epochs[epoch()].auction.maxDollarAmount) { _state.epochs[epoch()].auction.maxDollarAmount = couponDollarAmount; - } else if (couponDollarAmount < _state.epochs[epoch()].auction.minDollarAmount) { + } + + if (couponDollarAmount < _state.epochs[epoch()].auction.minDollarAmount) { _state.epochs[epoch()].auction.minDollarAmount = couponDollarAmount; } } diff --git a/protocol/contracts/mock/MockRegulator.sol b/protocol/contracts/mock/MockRegulator.sol index 11ac0b2d..699af18b 100644 --- a/protocol/contracts/mock/MockRegulator.sol +++ b/protocol/contracts/mock/MockRegulator.sol @@ -36,10 +36,6 @@ contract MockRegulator is MockComptroller, Regulator, Market { return epoch <= 5; } - /*function placeCouponAuctionBid(uint256 couponEpochExpiry, uint256 dollarAmount, uint256 maxCouponAmount) { - super.placeCouponAuctionBid(couponEpochExpiry, dollarAmount, maxCouponAmount); - }*/ - function settleCouponAuctionE() external { super.settleCouponAuction(); } diff --git a/protocol/test/dao/Regulator.test.js b/protocol/test/dao/Regulator.test.js index 4e455db0..97762af4 100644 --- a/protocol/test/dao/Regulator.test.js +++ b/protocol/test/dao/Regulator.test.js @@ -630,7 +630,7 @@ describe('Regulator', function () { await this.dollar.approve(this.regulator.address, 1000000, {from: userAddress4}); }); - it('is able to settle auction and generated internals and deincrement debt', async function () { + it('is able to settle auction and generated internals', async function () { // add some bidders this.result = await this.regulator.placeCouponAuctionBid(20, 1000, 50000, {from: userAddress}); this.result1 = await this.regulator.placeCouponAuctionBid(5, 2000, 50000, {from: userAddress2}); @@ -641,13 +641,10 @@ describe('Regulator', function () { expect(await this.regulator.getCouponAuctionBidsE.call()).to.be.bignumber.equal(new BN(4)); - //Need to fix expect(await this.regulator.getCouponAuctionMinExpiryE.call()).to.be.bignumber.equal(new BN(0)); expect(await this.regulator.getCouponAuctionMaxExpiryE.call()).to.be.bignumber.equal(new BN(100997)); - //Need to fix expect(await this.regulator.getCouponAuctionMinYieldE.call()).to.be.bignumber.equal(new BN(0)); expect(await this.regulator.getCouponAuctionMaxYieldE.call()).to.be.bignumber.equal(new BN(55)); - //Need to fix expect(await this.regulator.getCouponAuctionMinDollarAmountE.call()).to.be.bignumber.equal(new BN(0)); expect(await this.regulator.getCouponAuctionMaxDollarAmountE.call()).to.be.bignumber.equal(new BN(2000)); @@ -695,6 +692,50 @@ describe('Regulator', function () { }); }); + describe('when calling init again during auction', function () { + describe('auction is not finished and not canceled', function () { + beforeEach(async function () { + await this.regulator.mintToE(userAddress, 1000000); + await this.regulator.mintToE(userAddress2, 1000000); + await this.regulator.mintToE(userAddress3, 1000000); + await this.regulator.mintToE(userAddress4, 1000000); + await this.dollar.approve(this.regulator.address, 1000000, {from: userAddress}); + await this.dollar.approve(this.regulator.address, 1000000, {from: userAddress2}); + await this.dollar.approve(this.regulator.address, 1000000, {from: userAddress3}); + await this.dollar.approve(this.regulator.address, 1000000, {from: userAddress4}); + }); + + it('is able to settle auction and generated internals without resetting them', async function () { + // add some bidders + this.result = await this.regulator.placeCouponAuctionBid(20, 1000, 50000, {from: userAddress}); + this.result1 = await this.regulator.placeCouponAuctionBid(5, 2000, 50000, {from: userAddress2}); + //thise bidders will be rejected + this.result2 = await this.regulator.placeCouponAuctionBid(1000, 900, 50000, {from: userAddress3}); + this.result3 = await this.regulator.placeCouponAuctionBid(100990, 900, 50000, {from: userAddress4}); + this.auction_settlement = await this.regulator.settleCouponAuctionE(); + + await this.regulator.initCouponAuctionE.call(); + + expect(await this.regulator.getCouponAuctionBidsE.call()).to.be.bignumber.equal(new BN(4)); + expect(await this.regulator.getCouponAuctionMinExpiryE.call()).to.be.bignumber.equal(new BN(0)); + expect(await this.regulator.getCouponAuctionMaxExpiryE.call()).to.be.bignumber.equal(new BN(100997)); + expect(await this.regulator.getCouponAuctionMinYieldE.call()).to.be.bignumber.equal(new BN(0)); + expect(await this.regulator.getCouponAuctionMaxYieldE.call()).to.be.bignumber.equal(new BN(55)); + expect(await this.regulator.getCouponAuctionMinDollarAmountE.call()).to.be.bignumber.equal(new BN(0)); + expect(await this.regulator.getCouponAuctionMaxDollarAmountE.call()).to.be.bignumber.equal(new BN(2000)); + + expect(await this.regulator.getMinExpiryFilled(7)).to.be.bignumber.equal(new BN(12)); + expect(await this.regulator.getMaxExpiryFilled(7)).to.be.bignumber.equal(new BN(100997)); + expect(await this.regulator.getAvgExpiryFilled(7)).to.be.bignumber.equal(new BN(25510)); + expect(await this.regulator.getMinYieldFilled(7)).to.be.bignumber.equal(new BN(25)); + expect(await this.regulator.getMaxYieldFilled(7)).to.be.bignumber.equal(new BN(55)); + expect(await this.regulator.getAvgYieldFilled(7)).to.be.bignumber.equal(new BN(46)); + expect(await this.regulator.getBidToCover(7)).to.be.bignumber.equal(new BN(100)); + expect(await this.regulator.getTotalFilled(7)).to.be.bignumber.equal(new BN(4)); + }); + }); + }); + it('doesnt mint new Dollar tokens', async function () { expect(await this.dollar.totalSupply()).to.be.bignumber.equal(new BN(1000000)); expect(await this.dollar.balanceOf(this.regulator.address)).to.be.bignumber.equal(new BN(1000000)); diff --git a/protocol/test/dao/State.test.js b/protocol/test/dao/State.test.js index cb912335..1be80c53 100644 --- a/protocol/test/dao/State.test.js +++ b/protocol/test/dao/State.test.js @@ -4,7 +4,6 @@ const { BN, expectRevert, time } = require('@openzeppelin/test-helpers'); const { expect } = require('chai'); const MockState = contract.fromArtifact('MockState'); -const MockAuction = contract.fromArtifact('MockAuction'); const BOOTSTRAPPING_END_TIMESTAMP = 1600905600; const EPOCH_START = 1602288000; @@ -15,7 +14,6 @@ describe('State', function () { beforeEach(async function () { this.setters = await MockState.new({from: ownerAddress}); - this.auction = await MockAuction.new({from: ownerAddress}); }); /** From 5afc1253a0561a85d9d1a9f2e142342643842f3d Mon Sep 17 00:00:00 2001 From: cinquemb <1519017+cinquemb@users.noreply.github.com> Date: Sat, 9 Jan 2021 07:19:41 -0500 Subject: [PATCH 33/47] done adding tests --- protocol/contracts/mock/MockRegulator.sol | 41 ++++++++++++++++++++++- protocol/test/dao/Regulator.test.js | 17 +++++----- 2 files changed, 49 insertions(+), 9 deletions(-) diff --git a/protocol/contracts/mock/MockRegulator.sol b/protocol/contracts/mock/MockRegulator.sol index 699af18b..671a4eee 100644 --- a/protocol/contracts/mock/MockRegulator.sol +++ b/protocol/contracts/mock/MockRegulator.sol @@ -23,7 +23,9 @@ import "../oracle/IOracle.sol"; import "./MockComptroller.sol"; import "./MockState.sol"; -contract MockRegulator is MockComptroller, Regulator, Market { +contract MockRegulator is MockComptroller, Regulator { + bytes32 private constant FILE = "MockRegulator"; + constructor (address oracle, address pool) MockComptroller(pool) public { _state.provider.oracle = IOracle(oracle); } @@ -79,4 +81,41 @@ contract MockRegulator is MockComptroller, Regulator, Market { function getCouponAuctionMaxDollarAmountE() external returns (uint256) { return super.getCouponAuctionMaxDollarAmount(); } + + /* for testing only */ + + function placeCouponAuctionBid(uint256 couponEpochExpiry, uint256 dollarAmount, uint256 maxCouponAmount) external returns (bool) { + Require.that( + couponEpochExpiry > 0, + FILE, + "Must have non-zero expiry" + ); + + Require.that( + dollarAmount > 0, + FILE, + "Must bid non-zero amount" + ); + + Require.that( + maxCouponAmount > 0, + FILE, + "Must bid on non-zero amount" + ); + + Require.that( + totalDebt() >= dollarAmount, + FILE, + "Not enough debt" + ); + + uint256 epoch = epoch().add(couponEpochExpiry); + setCouponAuctionRelYield(maxCouponAmount.div(dollarAmount)); + setCouponAuctionRelDollarAmount(dollarAmount); + setCouponAuctionRelExpiry(epoch); + setCouponBidderState(msg.sender, epoch, dollarAmount, maxCouponAmount); + setCouponBidderStateIndex(getCouponAuctionBids(), msg.sender); + incrementCouponAuctionBids(); + return true; + } } diff --git a/protocol/test/dao/Regulator.test.js b/protocol/test/dao/Regulator.test.js index 97762af4..1990810a 100644 --- a/protocol/test/dao/Regulator.test.js +++ b/protocol/test/dao/Regulator.test.js @@ -29,6 +29,7 @@ describe('Regulator', function () { beforeEach(async function () { this.oracle = await MockSettableOracle.new({from: ownerAddress, gas: 8000000}); this.regulator = await MockRegulator.new(this.oracle.address, poolAddress, {from: ownerAddress, gas: 8000000}); + this.dollar = await Dollar.at(await this.regulator.dollar()); }); @@ -74,12 +75,12 @@ describe('Regulator', function () { expect(await this.regulator.totalCoupons()).to.be.bignumber.equal(new BN(0)); expect(await this.regulator.totalRedeemable()).to.be.bignumber.equal(new BN(0)); }); - + /* it('has not created any auction in the past 7 epochs', async function () { for(var a_idx = 1; a_idx<8; a_idx++){ expect(await this.regulator.isCouponAuctionInitAtEpochE.call(a_idx)).equal(false); } - }); + });*/ it('emits SupplyIncrease event', async function () { const event = await expectEvent.inTransaction(this.txHash, MockRegulator, 'SupplyIncrease', {}); @@ -641,11 +642,11 @@ describe('Regulator', function () { expect(await this.regulator.getCouponAuctionBidsE.call()).to.be.bignumber.equal(new BN(4)); - expect(await this.regulator.getCouponAuctionMinExpiryE.call()).to.be.bignumber.equal(new BN(0)); + expect(await this.regulator.getCouponAuctionMinExpiryE.call()).to.be.bignumber.equal(new BN(12)); expect(await this.regulator.getCouponAuctionMaxExpiryE.call()).to.be.bignumber.equal(new BN(100997)); - expect(await this.regulator.getCouponAuctionMinYieldE.call()).to.be.bignumber.equal(new BN(0)); + expect(await this.regulator.getCouponAuctionMinYieldE.call()).to.be.bignumber.equal(new BN(25)); expect(await this.regulator.getCouponAuctionMaxYieldE.call()).to.be.bignumber.equal(new BN(55)); - expect(await this.regulator.getCouponAuctionMinDollarAmountE.call()).to.be.bignumber.equal(new BN(0)); + expect(await this.regulator.getCouponAuctionMinDollarAmountE.call()).to.be.bignumber.equal(new BN(900)); expect(await this.regulator.getCouponAuctionMaxDollarAmountE.call()).to.be.bignumber.equal(new BN(2000)); expect(await this.regulator.getMinExpiryFilled(7)).to.be.bignumber.equal(new BN(12)); @@ -717,11 +718,11 @@ describe('Regulator', function () { await this.regulator.initCouponAuctionE.call(); expect(await this.regulator.getCouponAuctionBidsE.call()).to.be.bignumber.equal(new BN(4)); - expect(await this.regulator.getCouponAuctionMinExpiryE.call()).to.be.bignumber.equal(new BN(0)); + expect(await this.regulator.getCouponAuctionMinExpiryE.call()).to.be.bignumber.equal(new BN(12)); expect(await this.regulator.getCouponAuctionMaxExpiryE.call()).to.be.bignumber.equal(new BN(100997)); - expect(await this.regulator.getCouponAuctionMinYieldE.call()).to.be.bignumber.equal(new BN(0)); + expect(await this.regulator.getCouponAuctionMinYieldE.call()).to.be.bignumber.equal(new BN(25)); expect(await this.regulator.getCouponAuctionMaxYieldE.call()).to.be.bignumber.equal(new BN(55)); - expect(await this.regulator.getCouponAuctionMinDollarAmountE.call()).to.be.bignumber.equal(new BN(0)); + expect(await this.regulator.getCouponAuctionMinDollarAmountE.call()).to.be.bignumber.equal(new BN(900)); expect(await this.regulator.getCouponAuctionMaxDollarAmountE.call()).to.be.bignumber.equal(new BN(2000)); expect(await this.regulator.getMinExpiryFilled(7)).to.be.bignumber.equal(new BN(12)); From abe246549e955f981b3183a3238c6688290ca837 Mon Sep 17 00:00:00 2001 From: cinquemb <1519017+cinquemb@users.noreply.github.com> Date: Sat, 9 Jan 2021 08:16:04 -0500 Subject: [PATCH 34/47] add back in commented out test --- protocol/test/dao/Regulator.test.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/protocol/test/dao/Regulator.test.js b/protocol/test/dao/Regulator.test.js index 1990810a..beefa408 100644 --- a/protocol/test/dao/Regulator.test.js +++ b/protocol/test/dao/Regulator.test.js @@ -75,12 +75,11 @@ describe('Regulator', function () { expect(await this.regulator.totalCoupons()).to.be.bignumber.equal(new BN(0)); expect(await this.regulator.totalRedeemable()).to.be.bignumber.equal(new BN(0)); }); - /* it('has not created any auction in the past 7 epochs', async function () { for(var a_idx = 1; a_idx<8; a_idx++){ expect(await this.regulator.isCouponAuctionInitAtEpochE.call(a_idx)).equal(false); } - });*/ + }); it('emits SupplyIncrease event', async function () { const event = await expectEvent.inTransaction(this.txHash, MockRegulator, 'SupplyIncrease', {}); From 45db6c8b449d662bf9eccb0c0e263d067833debc Mon Sep 17 00:00:00 2001 From: cinquemb <1519017+cinquemb@users.noreply.github.com> Date: Fri, 15 Jan 2021 22:12:52 -0500 Subject: [PATCH 35/47] more parity between auction updates between dsd-esd --- protocol/contracts/Constants.sol | 6 ++ protocol/contracts/dao/Comptroller.sol | 4 ++ protocol/contracts/dao/Market.sol | 25 +++++-- protocol/contracts/dao/Regulator.sol | 90 +++++++++++++++----------- protocol/contracts/dao/Setters.sol | 73 +++++++++++++-------- 5 files changed, 129 insertions(+), 69 deletions(-) diff --git a/protocol/contracts/Constants.sol b/protocol/contracts/Constants.sol index a65bf88a..0da2351b 100644 --- a/protocol/contracts/Constants.sol +++ b/protocol/contracts/Constants.sol @@ -68,6 +68,8 @@ library Constants { /* Market */ uint256 private constant COUPON_EXPIRATION = 90; uint256 private constant DEBT_RATIO_CAP = 20e16; // 20% + uint256 private constant MAX_COUPON_YIELD_MULT = 2000; //2000 coupouns per 1 dollar burn + /* Regulator */ uint256 private constant SUPPLY_CHANGE_LIMIT = 3e16; // 3% @@ -165,6 +167,10 @@ library Constants { return COUPON_EXPIRATION; } + function getCouponMaxYieldToBurn() internal pure returns (uint256) { + return MAX_COUPON_YIELD_MULT; + } + function getDebtRatioCap() internal pure returns (Decimal.D256 memory) { return Decimal.D256({value: DEBT_RATIO_CAP}); } diff --git a/protocol/contracts/dao/Comptroller.sol b/protocol/contracts/dao/Comptroller.sol index fa05fa06..9e4ef4cb 100644 --- a/protocol/contracts/dao/Comptroller.sol +++ b/protocol/contracts/dao/Comptroller.sol @@ -122,6 +122,10 @@ contract Comptroller is Setters { return 0; } + function acceptableBidCheck(address account, uint256 dollarAmount) internal returns (bool) { + return (dollar().balanceOf(account) >= balanceOfBonded(account).add(dollarAmount)); + } + function balanceCheck() private { Require.that( dollar().balanceOf(address(this)) >= totalBonded().add(totalStaged()).add(totalRedeemable()), diff --git a/protocol/contracts/dao/Market.sol b/protocol/contracts/dao/Market.sol index 00615ca2..3ddf75c7 100644 --- a/protocol/contracts/dao/Market.sol +++ b/protocol/contracts/dao/Market.sol @@ -144,14 +144,29 @@ contract Market is Comptroller, Curve { "Not enough debt" ); - uint256 epoch = epoch().add(couponEpochExpiry); + Require.that( + acceptableBidCheck(msg.sender, dollarAmount), + FILE, + "Must have enough in account" + ); + + uint256 yield = maxCouponAmount.div(dollarAmount); + uint256 maxYield = Constants.getCouponMaxYieldToBurn(); + + Require.that( + maxYield >= yield, + FILE, + "Must be under maxYield" + ); + + uint256 epochExpiry = epoch().add(couponEpochExpiry); setCouponAuctionRelYield(maxCouponAmount.div(dollarAmount)); setCouponAuctionRelDollarAmount(dollarAmount); - setCouponAuctionRelExpiry(epoch); - setCouponBidderState(msg.sender, epoch, dollarAmount, maxCouponAmount); - setCouponBidderStateIndex(getCouponAuctionBids(), msg.sender); + setCouponAuctionRelExpiry(epochExpiry); + setCouponBidderState(uint256(epoch()), msg.sender, couponEpochExpiry, dollarAmount, maxCouponAmount); + setCouponBidderStateIndex(uint256(epoch()), getCouponAuctionBids(uint256(epoch())), msg.sender); incrementCouponAuctionBids(); - emit CouponBidPlaced(msg.sender, epoch, dollarAmount, maxCouponAmount); + emit CouponBidPlaced(msg.sender, epochExpiry, dollarAmount, maxCouponAmount); return true; } } diff --git a/protocol/contracts/dao/Regulator.sol b/protocol/contracts/dao/Regulator.sol index a1e58822..86ff4369 100644 --- a/protocol/contracts/dao/Regulator.sol +++ b/protocol/contracts/dao/Regulator.sol @@ -151,15 +151,15 @@ contract Regulator is Comptroller { return y; } - function settleCouponAuction() internal returns (bool success) { - if (!isCouponAuctionFinished() && !isCouponAuctionCanceled()) { - uint256 yieldRelNorm = getCouponAuctionMaxYield() - getCouponAuctionMinYield(); - uint256 expiryRelNorm = getCouponAuctionMaxExpiry() - getCouponAuctionMinExpiry(); - uint256 dollarRelNorm = getCouponAuctionMaxDollarAmount() - getCouponAuctionMinDollarAmount(); + function settleCouponAuction(uint256 settlementEpoch) internal returns (bool success) { + if (!isCouponAuctionFinished(settlementEpoch) && !isCouponAuctionCanceled(settlementEpoch)) { + yieldRelNorm = getCouponAuctionMaxYield(settlementEpoch) - getCouponAuctionMinYield(settlementEpoch); + expiryRelNorm = getCouponAuctionMaxExpiry(settlementEpoch) - getCouponAuctionMinExpiry(settlementEpoch); + dollarRelNorm = getCouponAuctionMaxDollarAmount(settlementEpoch) - getCouponAuctionMinDollarAmount(settlementEpoch); // loop over bids and compute distance - for (uint256 i = 0; i < getCouponAuctionBids(); i++) { - Epoch.CouponBidderState storage bidder = getCouponBidderState(getCouponBidderStateIndex(i)); + for (uint256 i = 0; i < getCouponAuctionBids(settlementEpoch); i++) { + Epoch.CouponBidderState storage bidder = getCouponBidderState(settlementEpoch, getCouponBidderStateIndex(settlementEpoch, i)); Decimal.D256 memory yieldRel = Decimal.ratio( Decimal.ratio( bidder.couponAmount, @@ -191,8 +191,8 @@ contract Regulator is Comptroller { distance = Decimal.zero(); } - setCouponBidderStateDistance(getCouponBidderStateIndex(i), distance); - bidder = getCouponBidderState(getCouponBidderStateIndex(i)); + setCouponBidderStateDistance(settlementEpoch, getCouponBidderStateIndex(settlementEpoch, i), distance); + bidder = getCouponBidderState(settlementEpoch, getCouponBidderStateIndex(settlementEpoch, i)); bids.push(bidder); } @@ -203,35 +203,42 @@ contract Regulator is Comptroller { // assign coupons until totalDebt filled, reject the rest for (uint256 i = 0; i < bids.length; i++) { if (totalDebt() >= bids[i].dollarAmount) { - if (!getCouponBidderStateRejected(bids[i].bidder) && !getCouponBidderStateRejected(bids[i].bidder)) { + if (!getCouponBidderStateRejected(settlementEpoch, bids[i].bidder) && !getCouponBidderStateRejected(settlementEpoch, bids[i].bidder)) { Decimal.D256 memory yield = Decimal.ratio( bids[i].couponAmount, bids[i].dollarAmount ); - - if (yield.lessThan(minYieldFilled)) { - minYieldFilled = yield; - } else if (yield.greaterThan(maxYieldFilled)) { - maxYieldFilled = yield; - } - if (bids[i].couponExpiryEpoch < minExpiryFilled) { - minExpiryFilled = bids[i].couponExpiryEpoch; - } else if (bids[i].couponExpiryEpoch > maxExpiryFilled) { - maxExpiryFilled = bids[i].couponExpiryEpoch; + //must check again if account is able to be assigned + if (acceptableBidCheck(bids[i].bidder, bids[i].dollarAmount)){ + if (yield.lessThan(minYieldFilled)) { + minYieldFilled = yield; + } else if (yield.greaterThan(maxYieldFilled)) { + maxYieldFilled = yield; + } + + if (bids[i].couponExpiryEpoch < minExpiryFilled) { + minExpiryFilled = bids[i].couponExpiryEpoch; + } else if (bids[i].couponExpiryEpoch > maxExpiryFilled) { + maxExpiryFilled = bids[i].couponExpiryEpoch; + } + + sumYieldFilled += yield.asUint256(); + sumExpiryFilled += bids[i].couponExpiryEpoch; + totalAuctioned += bids[i].couponAmount; + totalBurned += bids[i].dollarAmount; + + uint256 epochExpiry = epoch().add(bids[i].couponExpiryEpoch); + burnFromAccount(bids[i].bidder, bids[i].dollarAmount); + incrementBalanceOfCoupons(bids[i].bidder, epochExpiry, bids[i].couponAmount); + setCouponBidderStateSelected(settlementEpoch, bids[i].bidder, i); + totalFilled++; + } else { + setCouponBidderStateRejected(settlementEpoch, bids[i].bidder); } - - sumYieldFilled += yield.asUint256(); - sumExpiryFilled += bids[i].couponExpiryEpoch; - - uint256 epoch = epoch().add(bids[i].couponExpiryEpoch); - burnFromAccount(bids[i].bidder, bids[i].dollarAmount); - incrementBalanceOfCoupons(bids[i].bidder, epoch, bids[i].couponAmount); - setCouponBidderStateSelected(bids[i].bidder); - totalFilled++; } } else { - /* setCouponBidderStateRejected(bids[i].bidder); or just break and close the auction */ + /* setCouponBidderStateRejected(settlementEpoch, bids[i].bidder); or just break and close the auction */ break; } } @@ -253,19 +260,26 @@ contract Regulator is Comptroller { totalFilled ).mul(100); - setMinExpiryFilled(minExpiryFilled); - setMaxExpiryFilled(maxExpiryFilled); - setAvgExpiryFilled(avgExpiryFilled.asUint256()); - setMinYieldFilled(minYieldFilled.asUint256()); - setMaxYieldFilled(maxYieldFilled.asUint256()); - setAvgYieldFilled(avgYieldFilled.asUint256()); - setBidToCover(bidToCover.asUint256()); - setTotalFilled(totalFilled); + setMinExpiryFilled(settlementEpoch, minExpiryFilled); + setMaxExpiryFilled(settlementEpoch, maxExpiryFilled); + setAvgExpiryFilled(settlementEpoch, avgExpiryFilled.asUint256()); + setMinYieldFilled(settlementEpoch, minYieldFilled.asUint256()); + setMaxYieldFilled(settlementEpoch, maxYieldFilled.asUint256()); + setAvgYieldFilled(settlementEpoch, avgYieldFilled.asUint256()); + setBidToCover(settlementEpoch, bidToCover.asUint256()); + setTotalFilled(settlementEpoch, totalFilled); + setTotalAuctioned(settlementEpoch, totalAuctioned); + setTotalBurned(settlementEpoch, totalBurned); } //clear bids and reset vars delete bids; totalFilled = 0; + totalBurned = 0; + yieldRelNorm = 1; + expiryRelNorm = 1; + dollarRelNorm = 1; + totalAuctioned = 0; maxExpiryFilled = 0; sumExpiryFilled = 0; sumYieldFilled = 0; diff --git a/protocol/contracts/dao/Setters.sol b/protocol/contracts/dao/Setters.sol index cf1ae1e6..ad12d052 100644 --- a/protocol/contracts/dao/Setters.sol +++ b/protocol/contracts/dao/Setters.sol @@ -170,8 +170,8 @@ contract Setters is State, Getters { _state.epochs[epoch].auction.finished = true; } - function setCouponBidderState(address bidder, uint256 couponEpochExpiry, uint256 dollarAmount, uint256 maxCouponAmount) internal { - Epoch.CouponBidderState storage bidderState = _state.epochs[epoch()].auction.couponBidderState[bidder]; + function setCouponBidderState(uint256 epoch, address bidder, uint256 couponEpochExpiry, uint256 dollarAmount, uint256 maxCouponAmount) internal { + Epoch.CouponBidderState storage bidderState = _state.epochs[epoch].auction.couponBidderState[bidder]; bidderState.couponExpiryEpoch = couponEpochExpiry; bidderState.dollarAmount = dollarAmount; @@ -179,20 +179,25 @@ contract Setters is State, Getters { bidderState.bidder = bidder; } - function setCouponBidderStateDistance(address bidder, Decimal.D256 memory distance) internal { - _state.epochs[epoch()].auction.couponBidderState[bidder].distance = distance; + function setCouponBidderStateDistance(uint256 epoch, address bidder, Decimal.D256 memory distance) internal { + _state.epochs[epoch].auction.couponBidderState[bidder].distance = distance; } - function setCouponBidderStateSelected(address bidder) internal { - _state.epochs[epoch()].auction.couponBidderState[bidder].selected = true; + function setCouponBidderStateSelected(uint256 epoch, address bidder, uint256 index) internal { + _state.epochs[epoch].auction.couponBidderState[bidder].selected = true; + _state.epochs[epoch].auction.seletedCouponBidder[index] = bidder; } - function setCouponBidderStateRejected(address bidder) internal { - _state.epochs[epoch()].auction.couponBidderState[bidder].rejected = true; + function setCouponBidderStateRejected(uint256 epoch, address bidder) internal { + _state.epochs[epoch].auction.couponBidderState[bidder].rejected = true; } - function setCouponBidderStateIndex(uint256 index, address bidder) internal { - _state.epochs[epoch()].auction.couponBidder[index] = bidder; + function setCouponBidderStateRedeemed(uint256 epoch, address bidder) internal { + _state.epochs[epoch].auction.couponBidderState[bidder].redeemed = true; + } + + function setCouponBidderStateIndex(uint256 epoch, uint256 index, address bidder) internal { + _state.epochs[epoch].auction.couponBidder[index] = bidder; } function incrementCouponAuctionBids() internal { @@ -229,36 +234,52 @@ contract Setters is State, Getters { } } - function setMinExpiryFilled(uint256 minExpiryFilled) internal { - _state.epochs[epoch()].auction.minExpiryFilled = minExpiryFilled; + function setMinExpiryFilled(uint256 epoch, uint256 minExpiryFilled) internal { + _state.epochs[epoch].auction.minExpiryFilled = minExpiryFilled; } - function setMaxExpiryFilled(uint256 maxExpiryFilled) internal { - _state.epochs[epoch()].auction.maxExpiryFilled = maxExpiryFilled; + function setMaxExpiryFilled(uint256 epoch, uint256 maxExpiryFilled) internal { + _state.epochs[epoch].auction.maxExpiryFilled = maxExpiryFilled; } - function setAvgExpiryFilled(uint256 avgExpiryFilled) internal { - _state.epochs[epoch()].auction.avgExpiryFilled = avgExpiryFilled; + function setAvgExpiryFilled(uint256 epoch, uint256 avgExpiryFilled) internal { + _state.epochs[epoch].auction.avgExpiryFilled = avgExpiryFilled; } - function setMinYieldFilled(uint256 minYieldFilled) internal { - _state.epochs[epoch()].auction.minYieldFilled = minYieldFilled; + function setMinYieldFilled(uint256 epoch, uint256 minYieldFilled) internal { + _state.epochs[epoch].auction.minYieldFilled = minYieldFilled; } - function setMaxYieldFilled(uint256 maxYieldFilled) internal { - _state.epochs[epoch()].auction.maxYieldFilled = maxYieldFilled; + function setMaxYieldFilled(uint256 epoch, uint256 maxYieldFilled) internal { + _state.epochs[epoch].auction.maxYieldFilled = maxYieldFilled; } - function setAvgYieldFilled(uint256 avgYieldFilled) internal { - _state.epochs[epoch()].auction.avgYieldFilled = avgYieldFilled; + function setAvgYieldFilled(uint256 epoch, uint256 avgYieldFilled) internal { + _state.epochs[epoch].auction.avgYieldFilled = avgYieldFilled; } - function setBidToCover(uint256 bidToCover) internal { - _state.epochs[epoch()].auction.bidToCover = bidToCover; + function setBidToCover(uint256 epoch, uint256 bidToCover) internal { + _state.epochs[epoch].auction.bidToCover = bidToCover; } - function setTotalFilled(uint256 totalFilled) internal { - _state.epochs[epoch()].auction.totalFilled = totalFilled; + function setTotalFilled(uint256 epoch, uint256 totalFilled) internal { + _state.epochs[epoch].auction.totalFilled = totalFilled; + } + + function setTotalAuctioned(uint256 epoch, uint256 totalAuctioned) internal { + _state.epochs[epoch].auction.totalAuctioned = totalAuctioned; + } + + function setCouponAuctionStateDead(uint256 epoch) internal { + _state.epochs[epoch].auction.dead = true; + } + + function setTotalBurned(uint256 epoch, uint256 totalBurned) internal { + _state.epochs[epoch].auction.totalBurned = totalBurned; + } + + function setLatestDeadAuctionEpoch(uint256 epoch) internal { + _state.epoch.latestDeadAuction = epoch; } From faac8a0af98f05ac4765f810826ca11187b29149 Mon Sep 17 00:00:00 2001 From: cinquemb <1519017+cinquemb@users.noreply.github.com> Date: Fri, 15 Jan 2021 22:18:12 -0500 Subject: [PATCH 36/47] state parity --- protocol/contracts/dao/Getters.sol | 82 ++++++++++++++++++------------ protocol/contracts/dao/State.sol | 10 +++- 2 files changed, 58 insertions(+), 34 deletions(-) diff --git a/protocol/contracts/dao/Getters.sol b/protocol/contracts/dao/Getters.sol index c67f60d1..0e77d085 100644 --- a/protocol/contracts/dao/Getters.sol +++ b/protocol/contracts/dao/Getters.sol @@ -195,90 +195,106 @@ contract Getters is State { return _state.epochs[epoch].auction; } - function getCouponAuctionBids() internal view returns (uint256) { - return _state.epochs[epoch()].auction._totalBids; + function getCouponAuctionBids(uint256 epoch) internal view returns (uint256) { + return _state.epochs[epoch].auction._totalBids; } - function getCouponBidderState(address bidder) internal view returns (Epoch.CouponBidderState storage) { - return _state.epochs[epoch()].auction.couponBidderState[bidder]; + function getCouponBidderState(uint256 epoch, address bidder) internal view returns (Epoch.CouponBidderState storage) { + return _state.epochs[epoch].auction.couponBidderState[bidder]; } - function getCouponBidderStateSelected(address bidder) internal view returns (bool) { - return _state.epochs[epoch()].auction.couponBidderState[bidder].selected; + function getCouponBidderStateSelected(uint256 epoch, address bidder) internal view returns (bool) { + return _state.epochs[epoch].auction.couponBidderState[bidder].selected; } - function getCouponBidderStateRejected(address bidder) internal view returns (bool) { - return _state.epochs[epoch()].auction.couponBidderState[bidder].rejected; + function getCouponBidderStateAssginedAtIndex(uint256 epoch, uint256 index) internal view returns (address) { + return _state.epochs[epoch].auction.seletedCouponBidder[index]; } - function getCouponBidderStateIndex(uint256 index) internal view returns (address) { - return _state.epochs[epoch()].auction.couponBidder[index]; + function getCouponBidderStateRejected(uint256 epoch, address bidder) internal view returns (bool) { + return _state.epochs[epoch].auction.couponBidderState[bidder].rejected; } - function isCouponAuctionFinished() internal view returns (bool){ - return _state.epochs[epoch()].auction.finished; + function getCouponBidderStateIndex(uint256 epoch, uint256 index) internal view returns (address) { + return _state.epochs[epoch].auction.couponBidder[index]; } - function isCouponAuctionCanceled() internal view returns (bool){ - return _state.epochs[epoch()].auction.canceled; + function isCouponAuctionFinished(uint256 epoch) internal view returns (bool){ + return _state.epochs[epoch].auction.finished; } - function getCouponAuctionMinExpiry() internal view returns (uint256) { - return _state.epochs[epoch()].auction.minExpiry; + function isCouponAuctionCanceled(uint256 epoch) internal view returns (bool){ + return _state.epochs[epoch].auction.canceled; } - function getCouponAuctionMaxExpiry() internal view returns (uint256) { - return _state.epochs[epoch()].auction.maxExpiry; + function getCouponAuctionMinExpiry(uint256 epoch) internal view returns (uint256) { + return _state.epochs[epoch].auction.minExpiry; } - function getCouponAuctionMinYield() internal view returns (uint256) { - return _state.epochs[epoch()].auction.minYield; + function getCouponAuctionMaxExpiry(uint256 epoch) internal view returns (uint256) { + return _state.epochs[epoch].auction.maxExpiry; } - function getCouponAuctionMaxYield() internal view returns (uint256) { - return _state.epochs[epoch()].auction.maxYield; + function getCouponAuctionMinYield(uint256 epoch) internal view returns (uint256) { + return _state.epochs[epoch].auction.minYield; } - function getCouponAuctionMinDollarAmount() internal view returns (uint256) { - return _state.epochs[epoch()].auction.minDollarAmount; + function getCouponAuctionMaxYield(uint256 epoch) internal view returns (uint256) { + return _state.epochs[epoch].auction.maxYield; } - function getCouponAuctionMaxDollarAmount() internal view returns (uint256) { - return _state.epochs[epoch()].auction.maxDollarAmount; + function getCouponAuctionMinDollarAmount(uint256 epoch) internal view returns (uint256) { + return _state.epochs[epoch].auction.minDollarAmount; + } + + function getCouponAuctionMaxDollarAmount(uint256 epoch) internal view returns (uint256) { + return _state.epochs[epoch].auction.maxDollarAmount; } function getMinExpiryFilled(uint256 epoch) public view returns (uint256) { return _state.epochs[epoch].auction.minExpiryFilled; } - function getMaxExpiryFilled(uint256 epoch) public view returns (uint256) { + function getMaxExpiryFilled(uint256 epoch) public view returns (uint256) { return _state.epochs[epoch].auction.maxExpiryFilled; } - function getAvgExpiryFilled(uint256 epoch) public view returns (uint256) { + function getAvgExpiryFilled(uint256 epoch) public view returns (uint256) { return _state.epochs[epoch].auction.avgExpiryFilled; } - function getMinYieldFilled(uint256 epoch) public view returns (uint256) { + function getMinYieldFilled(uint256 epoch) public view returns (uint256) { return _state.epochs[epoch].auction.minYieldFilled; } - function getMaxYieldFilled(uint256 epoch) public view returns (uint256) { + function getMaxYieldFilled(uint256 epoch) public view returns (uint256) { return _state.epochs[epoch].auction.maxYieldFilled; } - function getAvgYieldFilled(uint256 epoch) public view returns (uint256) { + function getAvgYieldFilled(uint256 epoch) public view returns (uint256) { return _state.epochs[epoch].auction.avgYieldFilled; } - function getBidToCover(uint256 epoch) public view returns (uint256) { + function getBidToCover(uint256 epoch) public view returns (uint256) { return _state.epochs[epoch].auction.bidToCover; } - function getTotalFilled(uint256 epoch) public view returns (uint256) { + function getTotalFilled(uint256 epoch) public view returns (uint256) { return _state.epochs[epoch].auction.totalFilled; } + function getTotalAuctioned(uint256 epoch) public view returns (uint256) { + return _state.epochs[epoch].auction.totalAuctioned; + } + + function getTotalBurned(uint256 epoch) public view returns (uint256) { + return _state.epochs[epoch].auction.totalBurned; + } + + function getLatestDeadAuctionEpoch() public view returns (uint256) { + return _state.epoch.latestDeadAuction; + } + /** * Governance */ diff --git a/protocol/contracts/dao/State.sol b/protocol/contracts/dao/State.sol index d9013b5a..cab14d39 100644 --- a/protocol/contracts/dao/State.sol +++ b/protocol/contracts/dao/State.sol @@ -44,6 +44,7 @@ contract Epoch { uint256 start; uint256 period; uint256 current; + uint256 latestDeadAuction; } struct Coupons { @@ -53,16 +54,20 @@ contract Epoch { } struct CouponBidderState { + bool dead; bool selected; bool rejected; + bool redeemed; address bidder; uint256 dollarAmount; uint256 couponAmount; Decimal.D256 distance; uint256 couponExpiryEpoch; + uint256 couponRedemptionIndex; } struct AuctionState { + bool dead; bool isInit; bool canceled; bool finished; @@ -71,8 +76,10 @@ contract Epoch { uint256 minYield; uint256 maxYield; uint256 _totalBids; - uint256 totalFilled; uint256 bidToCover; + uint256 totalFilled; + uint256 totalBurned; + uint256 totalAuctioned; uint256 minYieldFilled; uint256 maxYieldFilled; uint256 avgYieldFilled; @@ -82,6 +89,7 @@ contract Epoch { uint256 minDollarAmount; uint256 maxDollarAmount; mapping(uint256 => address) couponBidder; + mapping(uint256 => address) seletedCouponBidder; mapping(address => CouponBidderState) couponBidderState; } From b29314e97a0f4359c5744753f162c1f7ae4ea77d Mon Sep 17 00:00:00 2001 From: cinquemb <1519017+cinquemb@users.noreply.github.com> Date: Fri, 15 Jan 2021 22:21:50 -0500 Subject: [PATCH 37/47] state parity --- protocol/contracts/dao/Regulator.sol | 7 ++++++- protocol/contracts/mock/MockRegulator.sol | 24 +++++++++++++++++++---- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/protocol/contracts/dao/Regulator.sol b/protocol/contracts/dao/Regulator.sol index 86ff4369..f7c36ca3 100644 --- a/protocol/contracts/dao/Regulator.sol +++ b/protocol/contracts/dao/Regulator.sol @@ -29,6 +29,11 @@ contract Regulator is Comptroller { bytes32 private constant FILE = "Regulator"; Epoch.CouponBidderState[] private bids; uint256 private totalFilled = 0; + uint256 private totalBurned = 0; + uint256 private yieldRelNorm = 1; + uint256 private expiryRelNorm = 1; + uint256 private dollarRelNorm = 1; + uint256 private totalAuctioned = 0; uint256 private maxExpiryFilled = 0; uint256 private sumExpiryFilled = 0; uint256 private sumYieldFilled = 0; @@ -59,7 +64,7 @@ contract Regulator is Comptroller { if (price.lessThan(Decimal.one())) { //check for outstanding auction, if exists settle it and start a new one if (auction.isInit == true){ - bool isAuctionSettled = settleCouponAuction(); + bool isAuctionSettled = settleCouponAuction(epoch() - 1); finishCouponAuctionAtEpoch(epoch() - 1); } initCouponAuction(); diff --git a/protocol/contracts/mock/MockRegulator.sol b/protocol/contracts/mock/MockRegulator.sol index 671a4eee..49c00b16 100644 --- a/protocol/contracts/mock/MockRegulator.sol +++ b/protocol/contracts/mock/MockRegulator.sol @@ -109,13 +109,29 @@ contract MockRegulator is MockComptroller, Regulator { "Not enough debt" ); - uint256 epoch = epoch().add(couponEpochExpiry); + Require.that( + acceptableBidCheck(msg.sender, dollarAmount), + FILE, + "Must have enough in account" + ); + + uint256 yield = maxCouponAmount.div(dollarAmount); + uint256 maxYield = Constants.getCouponMaxYieldToBurn(); + + Require.that( + maxYield >= yield, + FILE, + "Must be under maxYield" + ); + + uint256 epochExpiry = epoch().add(couponEpochExpiry); setCouponAuctionRelYield(maxCouponAmount.div(dollarAmount)); setCouponAuctionRelDollarAmount(dollarAmount); - setCouponAuctionRelExpiry(epoch); - setCouponBidderState(msg.sender, epoch, dollarAmount, maxCouponAmount); - setCouponBidderStateIndex(getCouponAuctionBids(), msg.sender); + setCouponAuctionRelExpiry(epochExpiry); + setCouponBidderState(uint256(epoch()), msg.sender, couponEpochExpiry, dollarAmount, maxCouponAmount); + setCouponBidderStateIndex(uint256(epoch()), getCouponAuctionBids(uint256(epoch())), msg.sender); incrementCouponAuctionBids(); + emit CouponBidPlaced(msg.sender, epochExpiry, dollarAmount, maxCouponAmount); return true; } } From 3476961c691799e70a347bdcfc4f3a00c5cbd785 Mon Sep 17 00:00:00 2001 From: cinquemb <1519017+cinquemb@users.noreply.github.com> Date: Fri, 15 Jan 2021 22:27:20 -0500 Subject: [PATCH 38/47] state parity --- protocol/contracts/mock/MockRegulator.sol | 33 +++++++++++------------ 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/protocol/contracts/mock/MockRegulator.sol b/protocol/contracts/mock/MockRegulator.sol index 49c00b16..daad48f5 100644 --- a/protocol/contracts/mock/MockRegulator.sol +++ b/protocol/contracts/mock/MockRegulator.sol @@ -38,8 +38,8 @@ contract MockRegulator is MockComptroller, Regulator { return epoch <= 5; } - function settleCouponAuctionE() external { - super.settleCouponAuction(); + function settleCouponAuctionE(uint256 epoch) external { + super.settleCouponAuction(epoch); } function cancelCouponAuctionAtEpochE(uint256 epoch) external { @@ -54,32 +54,32 @@ contract MockRegulator is MockComptroller, Regulator { super.initCouponAuction(); } - function getCouponAuctionBidsE() external returns (uint256) { - return super.getCouponAuctionBids(); + function getCouponAuctionBidsE(uint256 epoch) external returns (uint256) { + return super.getCouponAuctionBids(epoch); } - function getCouponAuctionMinExpiryE() external returns (uint256) { - return super.getCouponAuctionMinExpiry(); + function getCouponAuctionMinExpiryE(uint256 epoch) external returns (uint256) { + return super.getCouponAuctionMinExpiry(epoch); } - function getCouponAuctionMaxExpiryE() external returns (uint256) { - return super.getCouponAuctionMaxExpiry(); + function getCouponAuctionMaxExpiryE(uint256 epoch) external returns (uint256) { + return super.getCouponAuctionMaxExpiry(epoch); } - function getCouponAuctionMinYieldE() external returns (uint256) { - return super.getCouponAuctionMinYield(); + function getCouponAuctionMinYieldE(uint256 epoch) external returns (uint256) { + return super.getCouponAuctionMinYield(epoch); } - function getCouponAuctionMaxYieldE() external returns (uint256) { - return super.getCouponAuctionMaxYield(); + function getCouponAuctionMaxYieldE(uint256 epoch) external returns (uint256) { + return super.getCouponAuctionMaxYield(epoch); } - function getCouponAuctionMinDollarAmountE() external returns (uint256) { - return super.getCouponAuctionMinDollarAmount(); + function getCouponAuctionMinDollarAmountE(uint256 epoch) external returns (uint256) { + return super.getCouponAuctionMinDollarAmount(epoch); } - function getCouponAuctionMaxDollarAmountE() external returns (uint256) { - return super.getCouponAuctionMaxDollarAmount(); + function getCouponAuctionMaxDollarAmountE(uint256 epoch) external returns (uint256) { + return super.getCouponAuctionMaxDollarAmount(epoch); } /* for testing only */ @@ -131,7 +131,6 @@ contract MockRegulator is MockComptroller, Regulator { setCouponBidderState(uint256(epoch()), msg.sender, couponEpochExpiry, dollarAmount, maxCouponAmount); setCouponBidderStateIndex(uint256(epoch()), getCouponAuctionBids(uint256(epoch())), msg.sender); incrementCouponAuctionBids(); - emit CouponBidPlaced(msg.sender, epochExpiry, dollarAmount, maxCouponAmount); return true; } } From e3354f374464500047e66f88b6e31eadfb3980d6 Mon Sep 17 00:00:00 2001 From: cinquemb <1519017+cinquemb@users.noreply.github.com> Date: Sat, 16 Jan 2021 01:15:01 -0500 Subject: [PATCH 39/47] fixing regulator tests --- protocol/test/dao/Regulator.test.js | 66 ++++++++++++++--------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/protocol/test/dao/Regulator.test.js b/protocol/test/dao/Regulator.test.js index beefa408..5e2edc69 100644 --- a/protocol/test/dao/Regulator.test.js +++ b/protocol/test/dao/Regulator.test.js @@ -1,6 +1,6 @@ const { accounts, contract } = require('@openzeppelin/test-environment'); -const { BN, expectEvent } = require('@openzeppelin/test-helpers'); +const { BN, expectEvent, expectRevert } = require('@openzeppelin/test-helpers'); const { expect } = require('chai'); const MockRegulator = contract.fromArtifact('MockRegulator'); @@ -623,11 +623,11 @@ describe('Regulator', function () { await this.regulator.mintToE(userAddress, 1000000); await this.regulator.mintToE(userAddress2, 1000000); await this.regulator.mintToE(userAddress3, 1000000); - await this.regulator.mintToE(userAddress4, 1000000); + //await this.regulator.mintToE(userAddress4, 1000000); await this.dollar.approve(this.regulator.address, 1000000, {from: userAddress}); await this.dollar.approve(this.regulator.address, 1000000, {from: userAddress2}); await this.dollar.approve(this.regulator.address, 1000000, {from: userAddress3}); - await this.dollar.approve(this.regulator.address, 1000000, {from: userAddress4}); + //await this.dollar.approve(this.regulator.address, 1000000, {from: userAddress4}); }); it('is able to settle auction and generated internals', async function () { @@ -636,40 +636,40 @@ describe('Regulator', function () { this.result1 = await this.regulator.placeCouponAuctionBid(5, 2000, 50000, {from: userAddress2}); //thise bidders will be rejected this.result2 = await this.regulator.placeCouponAuctionBid(1000, 900, 50000, {from: userAddress3}); - this.result3 = await this.regulator.placeCouponAuctionBid(100990, 900, 50000, {from: userAddress4}); - this.auction_settlement = await this.regulator.settleCouponAuctionE(); + await expectRevert(this.regulator.placeCouponAuctionBid(100990, 900, 50000, {from: userAddress4}), "MockRegulator: Must have enough in account"); + this.auction_settlement = await this.regulator.settleCouponAuctionE(7); - expect(await this.regulator.getCouponAuctionBidsE.call()).to.be.bignumber.equal(new BN(4)); - expect(await this.regulator.getCouponAuctionMinExpiryE.call()).to.be.bignumber.equal(new BN(12)); - expect(await this.regulator.getCouponAuctionMaxExpiryE.call()).to.be.bignumber.equal(new BN(100997)); - expect(await this.regulator.getCouponAuctionMinYieldE.call()).to.be.bignumber.equal(new BN(25)); - expect(await this.regulator.getCouponAuctionMaxYieldE.call()).to.be.bignumber.equal(new BN(55)); - expect(await this.regulator.getCouponAuctionMinDollarAmountE.call()).to.be.bignumber.equal(new BN(900)); - expect(await this.regulator.getCouponAuctionMaxDollarAmountE.call()).to.be.bignumber.equal(new BN(2000)); - - expect(await this.regulator.getMinExpiryFilled(7)).to.be.bignumber.equal(new BN(12)); - expect(await this.regulator.getMaxExpiryFilled(7)).to.be.bignumber.equal(new BN(100997)); - expect(await this.regulator.getAvgExpiryFilled(7)).to.be.bignumber.equal(new BN(25510)); + expect(await this.regulator.getCouponAuctionBidsE.call(7)).to.be.bignumber.equal(new BN(3)); + expect(await this.regulator.getCouponAuctionMinExpiryE.call(7)).to.be.bignumber.equal(new BN(12)); + expect(await this.regulator.getCouponAuctionMaxExpiryE.call(7)).to.be.bignumber.equal(new BN(1007)); + expect(await this.regulator.getCouponAuctionMinYieldE.call(7)).to.be.bignumber.equal(new BN(25)); + expect(await this.regulator.getCouponAuctionMaxYieldE.call(7)).to.be.bignumber.equal(new BN(55)); + expect(await this.regulator.getCouponAuctionMinDollarAmountE.call(7)).to.be.bignumber.equal(new BN(900)); + expect(await this.regulator.getCouponAuctionMaxDollarAmountE.call(7)).to.be.bignumber.equal(new BN(2000)); + + expect(await this.regulator.getMinExpiryFilled(7)).to.be.bignumber.equal(new BN(5)); + expect(await this.regulator.getMaxExpiryFilled(7)).to.be.bignumber.equal(new BN(1000)); + expect(await this.regulator.getAvgExpiryFilled(7)).to.be.bignumber.equal(new BN(341)); expect(await this.regulator.getMinYieldFilled(7)).to.be.bignumber.equal(new BN(25)); expect(await this.regulator.getMaxYieldFilled(7)).to.be.bignumber.equal(new BN(55)); - expect(await this.regulator.getAvgYieldFilled(7)).to.be.bignumber.equal(new BN(46)); + expect(await this.regulator.getAvgYieldFilled(7)).to.be.bignumber.equal(new BN(43)); expect(await this.regulator.getBidToCover(7)).to.be.bignumber.equal(new BN(100)); - expect(await this.regulator.getTotalFilled(7)).to.be.bignumber.equal(new BN(4)); + expect(await this.regulator.getTotalFilled(7)).to.be.bignumber.equal(new BN(3)); }); }); describe('auction is finished', function () { beforeEach(async function () { //finish the auction - await this.regulator.finishCouponAuctionAtEpochE(1); + await this.regulator.finishCouponAuctionAtEpochE(7); }); it('is able to not settle auction', async function () { // add some bidders this.result = await this.regulator.placeCouponAuctionBid(20, 1000, 50000, {from: userAddress}); this.result1 = await this.regulator.placeCouponAuctionBid(5, 2000, 50000, {from: userAddress2}); - this.auction_settlement = await this.regulator.settleCouponAuctionE.call(); + this.auction_settlement = await this.regulator.settleCouponAuctionE.call(7); expect(this.auction_settlement).to.be.equal(false); }); @@ -685,7 +685,7 @@ describe('Regulator', function () { // add some bidders this.result = await this.regulator.placeCouponAuctionBid(20, 1000, 50000, {from: userAddress}); this.result1 = await this.regulator.placeCouponAuctionBid(5, 2000, 50000, {from: userAddress2}); - this.auction_settlement = await this.regulator.settleCouponAuctionE.call(); + this.auction_settlement = await this.regulator.settleCouponAuctionE.call(7); expect(this.auction_settlement).to.be.equal(false); }); @@ -712,21 +712,21 @@ describe('Regulator', function () { //thise bidders will be rejected this.result2 = await this.regulator.placeCouponAuctionBid(1000, 900, 50000, {from: userAddress3}); this.result3 = await this.regulator.placeCouponAuctionBid(100990, 900, 50000, {from: userAddress4}); - this.auction_settlement = await this.regulator.settleCouponAuctionE(); + this.auction_settlement = await this.regulator.settleCouponAuctionE(7); await this.regulator.initCouponAuctionE.call(); - expect(await this.regulator.getCouponAuctionBidsE.call()).to.be.bignumber.equal(new BN(4)); - expect(await this.regulator.getCouponAuctionMinExpiryE.call()).to.be.bignumber.equal(new BN(12)); - expect(await this.regulator.getCouponAuctionMaxExpiryE.call()).to.be.bignumber.equal(new BN(100997)); - expect(await this.regulator.getCouponAuctionMinYieldE.call()).to.be.bignumber.equal(new BN(25)); - expect(await this.regulator.getCouponAuctionMaxYieldE.call()).to.be.bignumber.equal(new BN(55)); - expect(await this.regulator.getCouponAuctionMinDollarAmountE.call()).to.be.bignumber.equal(new BN(900)); - expect(await this.regulator.getCouponAuctionMaxDollarAmountE.call()).to.be.bignumber.equal(new BN(2000)); - - expect(await this.regulator.getMinExpiryFilled(7)).to.be.bignumber.equal(new BN(12)); - expect(await this.regulator.getMaxExpiryFilled(7)).to.be.bignumber.equal(new BN(100997)); - expect(await this.regulator.getAvgExpiryFilled(7)).to.be.bignumber.equal(new BN(25510)); + expect(await this.regulator.getCouponAuctionBidsE.call(7)).to.be.bignumber.equal(new BN(4)); + expect(await this.regulator.getCouponAuctionMinExpiryE.call(7)).to.be.bignumber.equal(new BN(12)); + expect(await this.regulator.getCouponAuctionMaxExpiryE.call(7)).to.be.bignumber.equal(new BN(100997)); + expect(await this.regulator.getCouponAuctionMinYieldE.call(7)).to.be.bignumber.equal(new BN(25)); + expect(await this.regulator.getCouponAuctionMaxYieldE.call(7)).to.be.bignumber.equal(new BN(55)); + expect(await this.regulator.getCouponAuctionMinDollarAmountE.call(7)).to.be.bignumber.equal(new BN(900)); + expect(await this.regulator.getCouponAuctionMaxDollarAmountE.call(7)).to.be.bignumber.equal(new BN(2000)); + + expect(await this.regulator.getMinExpiryFilled(7)).to.be.bignumber.equal(new BN(5)); + expect(await this.regulator.getMaxExpiryFilled(7)).to.be.bignumber.equal(new BN(100990)); + expect(await this.regulator.getAvgExpiryFilled(7)).to.be.bignumber.equal(new BN(25503)); expect(await this.regulator.getMinYieldFilled(7)).to.be.bignumber.equal(new BN(25)); expect(await this.regulator.getMaxYieldFilled(7)).to.be.bignumber.equal(new BN(55)); expect(await this.regulator.getAvgYieldFilled(7)).to.be.bignumber.equal(new BN(46)); From cb2bc5076cbb78436b93128626e1bdaaa7491cce Mon Sep 17 00:00:00 2001 From: cinquemb <1519017+cinquemb@users.noreply.github.com> Date: Sat, 16 Jan 2021 03:35:27 -0500 Subject: [PATCH 40/47] adding back in autoredemption, prioritizing cross auction best bidders untill total redeembale is emptied - need to make total redeemable a function of previous non dead auction internals --- protocol/contracts/dao/Getters.sol | 4 ++ protocol/contracts/dao/Regulator.sol | 103 +++++++++++++++++++++++++++ protocol/contracts/dao/Setters.sol | 4 ++ protocol/contracts/dao/State.sol | 1 + 4 files changed, 112 insertions(+) diff --git a/protocol/contracts/dao/Getters.sol b/protocol/contracts/dao/Getters.sol index 0e77d085..efffc7f8 100644 --- a/protocol/contracts/dao/Getters.sol +++ b/protocol/contracts/dao/Getters.sol @@ -295,6 +295,10 @@ contract Getters is State { return _state.epoch.latestDeadAuction; } + function getLatestCouponAuctionRedeemedSelectedBidderIndex(uint256 epoch) public view returns (uint256) { + return _state.epochs[epoch].auction.latestRedeemedSelectedBidderIndex; + } + /** * Governance */ diff --git a/protocol/contracts/dao/Regulator.sol b/protocol/contracts/dao/Regulator.sol index f7c36ca3..653bcc77 100644 --- a/protocol/contracts/dao/Regulator.sol +++ b/protocol/contracts/dao/Regulator.sol @@ -297,4 +297,107 @@ contract Regulator is Comptroller { return false; } } + + function autoRedeemFromCouponAuction() internal returns (bool success) { + /* + WARNING: may need fundemental constraints in order to cap max run time as epocs grow? (i.e totalRedeemable needs to be a function of auction internals of non dead auctions when twap > 1) + */ + + // this will allow us to reloop over best bidders in each auction + while (totalRedeemable() > 0) { + bool willRedeemableOverflow = false; + // loop over past epochs from the latest `dead` epoch to the current + for (uint256 d_idx = getLatestDeadAuctionEpoch(); d_idx < uint256(epoch()); d_idx++) { + uint256 temp_coupon_auction_epoch = d_idx; + Epoch.AuctionState storage auction = getCouponAuctionAtEpoch(temp_coupon_auction_epoch); + + // skip auctions that have been canceled and or dead or no auction present? + if (!auction.canceled && !auction.dead && auction.isInit) { + if (auction.finished) { + + uint256 totalCurrentlyTriedRedeemed = 0; + // loop over bidders in order of assigned per epoch and redeem automatically untill capp is filled for epoch, mark those bids as redeemed, + + for (uint256 s_idx = getLatestCouponAuctionRedeemedSelectedBidderIndex(temp_coupon_auction_epoch); s_idx < getTotalFilled(temp_coupon_auction_epoch); s_idx++) { + address bidderAddress = getCouponBidderStateAssginedAtIndex(temp_coupon_auction_epoch, s_idx); + Epoch.CouponBidderState storage bidder = getCouponBidderState(temp_coupon_auction_epoch, bidderAddress); + + // skip over those bids that have already been redeemed + if (bidder.redeemed) { + totalCurrentlyTriedRedeemed++; + continue; + } + + uint256 totalRedeemable = totalRedeemable(); + + if (totalRedeemable > bidder.couponAmount) { + /* TODO + - need to make sure this is "safe" (i.e. it should NOT revert and undo all the previous redemptions, just break and skip while still incrementing total redeemed tried count) + */ + uint256 couponExpiryEpoch = temp_coupon_auction_epoch.add(bidder.couponExpiryEpoch); + + if (couponExpiryEpoch > uint256(couponExpiryEpoch)) { + //check if coupons for epoch are expired already + totalCurrentlyTriedRedeemed++; + setCouponBidderStateRedeemed(couponExpiryEpoch, bidderAddress); + continue; + } + + uint256 couponBalance = balanceOfCoupons(bidderAddress, couponExpiryEpoch); + + if (couponBalance > 0) { + uint256 minCouponAmount = 0; + if (couponBalance >= bidder.couponAmount) { + minCouponAmount = bidder.couponAmount; + } else { + minCouponAmount = couponBalance; + } + + decrementBalanceOfCoupons(bidderAddress, couponExpiryEpoch, minCouponAmount, "Regulator: Insufficient coupon balance"); + + redeemToAccount(bidderAddress, minCouponAmount); + + setCouponBidderStateRedeemed(couponExpiryEpoch, bidderAddress); + // set the next bidder in line + setLatestCouponAuctionRedeemedSelectedBidderIndex(temp_coupon_auction_epoch, s_idx + 1); + totalCurrentlyTriedRedeemed++; + + // time to jump into next auctions bidders + break; + } else { + // mark as redeemd if couponBalance is zero + setCouponBidderStateRedeemed(couponExpiryEpoch, bidderAddress); + // set the next bidder in line + setLatestCouponAuctionRedeemedSelectedBidderIndex(temp_coupon_auction_epoch, s_idx + 1); + totalCurrentlyTriedRedeemed++; + + // time to jump into next auctions bidders + break; + } + } else { + // no point in trying to redeem more if quota for epoch is done + willRedeemableOverflow = true; + break; + } + } + + /* + - if all have been tried to be redeemd or expired, market auction as `dead` + + */ + + if (totalCurrentlyTriedRedeemed == getTotalFilled(temp_coupon_auction_epoch)) { + setLatestDeadAuctionEpoch(temp_coupon_auction_epoch); + setCouponAuctionStateDead(temp_coupon_auction_epoch); + } + } + } + } + + if (willRedeemableOverflow) { + // stop trying to redeem across auctions + break; + } + } + } } diff --git a/protocol/contracts/dao/Setters.sol b/protocol/contracts/dao/Setters.sol index ad12d052..58e85414 100644 --- a/protocol/contracts/dao/Setters.sol +++ b/protocol/contracts/dao/Setters.sol @@ -281,6 +281,10 @@ contract Setters is State, Getters { function setLatestDeadAuctionEpoch(uint256 epoch) internal { _state.epoch.latestDeadAuction = epoch; } + + function setLatestCouponAuctionRedeemedSelectedBidderIndex(uint256 epoch, uint256 index) internal { + _state.epochs[epoch].auction.latestRedeemedSelectedBidderIndex = index; + } /** diff --git a/protocol/contracts/dao/State.sol b/protocol/contracts/dao/State.sol index cab14d39..3707aba1 100644 --- a/protocol/contracts/dao/State.sol +++ b/protocol/contracts/dao/State.sol @@ -89,6 +89,7 @@ contract Epoch { uint256 minDollarAmount; uint256 maxDollarAmount; mapping(uint256 => address) couponBidder; + uint256 latestRedeemedSelectedBidderIndex; mapping(uint256 => address) seletedCouponBidder; mapping(address => CouponBidderState) couponBidderState; } From 98063934d42cb9ec2bdd869a8366f856808e61ef Mon Sep 17 00:00:00 2001 From: cinquemb <1519017+cinquemb@users.noreply.github.com> Date: Sat, 16 Jan 2021 03:48:18 -0500 Subject: [PATCH 41/47] some comment stuff --- protocol/contracts/dao/Regulator.sol | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/protocol/contracts/dao/Regulator.sol b/protocol/contracts/dao/Regulator.sol index 653bcc77..d9e781bf 100644 --- a/protocol/contracts/dao/Regulator.sol +++ b/protocol/contracts/dao/Regulator.sol @@ -381,11 +381,8 @@ contract Regulator is Comptroller { } } - /* - - if all have been tried to be redeemd or expired, market auction as `dead` - - */ - + // if all have been tried to be redeemd or expired, mark auction as `dead` + if (totalCurrentlyTriedRedeemed == getTotalFilled(temp_coupon_auction_epoch)) { setLatestDeadAuctionEpoch(temp_coupon_auction_epoch); setCouponAuctionStateDead(temp_coupon_auction_epoch); @@ -395,7 +392,7 @@ contract Regulator is Comptroller { } if (willRedeemableOverflow) { - // stop trying to redeem across auctions + // stop trying to redeem accross auctions break; } } From 0fb939e958d953fe69a922594cdc3a1d84464012 Mon Sep 17 00:00:00 2001 From: cinquemb <1519017+cinquemb@users.noreply.github.com> Date: Sat, 16 Jan 2021 05:12:08 -0500 Subject: [PATCH 42/47] dont limit auction bids based on debt, grow supply based on auction internals --- protocol/contracts/dao/Comptroller.sol | 3 +- protocol/contracts/dao/Getters.sol | 21 ++++++- protocol/contracts/dao/Regulator.sol | 79 ++++++++++++-------------- protocol/contracts/dao/Setters.sol | 4 +- protocol/contracts/dao/State.sol | 2 +- 5 files changed, 61 insertions(+), 48 deletions(-) diff --git a/protocol/contracts/dao/Comptroller.sol b/protocol/contracts/dao/Comptroller.sol index 9e4ef4cb..2b89baf5 100644 --- a/protocol/contracts/dao/Comptroller.sol +++ b/protocol/contracts/dao/Comptroller.sol @@ -38,7 +38,8 @@ contract Comptroller is Setters { function burnFromAccount(address account, uint256 amount) internal { dollar().transferFrom(account, address(this), amount); dollar().burn(amount); - decrementTotalDebt(amount, "Comptroller: not enough outstanding debt"); + // debt doest matter + // decrementTotalDebt(amount, "Comptroller: not enough outstanding debt"); balanceCheck(); } diff --git a/protocol/contracts/dao/Getters.sol b/protocol/contracts/dao/Getters.sol index efffc7f8..ead4decc 100644 --- a/protocol/contracts/dao/Getters.sol +++ b/protocol/contracts/dao/Getters.sol @@ -291,14 +291,31 @@ contract Getters is State { return _state.epochs[epoch].auction.totalBurned; } - function getLatestDeadAuctionEpoch() public view returns (uint256) { - return _state.epoch.latestDeadAuction; + function getEarliestDeadAuctionEpoch() public view returns (uint256) { + return _state.epoch.earliestDeadAuction; } function getLatestCouponAuctionRedeemedSelectedBidderIndex(uint256 epoch) public view returns (uint256) { return _state.epochs[epoch].auction.latestRedeemedSelectedBidderIndex; } + function getAvgAvgYieldAcrossCouponAuctions() public view returns (uint256) { + // loop over past epochs from the latest `dead` epoch to the current + uint256 sumYield = 0; + uint256 totalAvailableAuctions = 1; + for (uint256 d_idx = getEarliestDeadAuctionEpoch(); d_idx < uint256(epoch()); d_idx++) { + uint256 temp_coupon_auction_epoch = d_idx; + Epoch.AuctionState storage auction = getCouponAuctionAtEpoch(temp_coupon_auction_epoch); + // skip auctions that have been canceled, dead or not finished auction present? + if (!auction.canceled && !auction.dead && auction.isInit && !auction.finished) { + sumYield += getAvgYieldFilled(temp_coupon_auction_epoch); + totalAvailableAuctions++; + } + } + + return sumYield.div(totalAvailableAuctions); + } + /** * Governance */ diff --git a/protocol/contracts/dao/Regulator.sol b/protocol/contracts/dao/Regulator.sol index d9e781bf..2ba0b34d 100644 --- a/protocol/contracts/dao/Regulator.sol +++ b/protocol/contracts/dao/Regulator.sol @@ -69,7 +69,6 @@ contract Regulator is Comptroller { } initCouponAuction(); - shrinkSupply(price); return; } @@ -88,7 +87,7 @@ contract Regulator is Comptroller { function growSupply(Decimal.D256 memory price) private { uint256 lessDebt = resetDebt(Decimal.zero()); - Decimal.D256 memory delta = limit(price.sub(Decimal.one()), price); + Decimal.D256 memory delta = Decimal.ratio(Decimal.one(), getAvgAvgYieldAcrossCouponAuctions()); uint256 newSupply = delta.mul(totalNet()).asUint256(); (uint256 newRedeemable, uint256 newBonded) = increaseSupply(newSupply); emit SupplyIncrease(epoch(), price.value, newRedeemable, lessDebt, newBonded); @@ -205,47 +204,43 @@ contract Regulator is Comptroller { // sort bids bids = sortBidsByDistance(bids); - // assign coupons until totalDebt filled, reject the rest + // assign coupons in order of bid preference for (uint256 i = 0; i < bids.length; i++) { - if (totalDebt() >= bids[i].dollarAmount) { - if (!getCouponBidderStateRejected(settlementEpoch, bids[i].bidder) && !getCouponBidderStateRejected(settlementEpoch, bids[i].bidder)) { - Decimal.D256 memory yield = Decimal.ratio( - bids[i].couponAmount, - bids[i].dollarAmount - ); - - //must check again if account is able to be assigned - if (acceptableBidCheck(bids[i].bidder, bids[i].dollarAmount)){ - if (yield.lessThan(minYieldFilled)) { - minYieldFilled = yield; - } else if (yield.greaterThan(maxYieldFilled)) { - maxYieldFilled = yield; - } + if (!getCouponBidderStateRejected(settlementEpoch, bids[i].bidder) && !getCouponBidderStateRejected(settlementEpoch, bids[i].bidder)) { + Decimal.D256 memory yield = Decimal.ratio( + bids[i].couponAmount, + bids[i].dollarAmount + ); + + //must check again if account is able to be assigned + if (acceptableBidCheck(bids[i].bidder, bids[i].dollarAmount)){ + if (yield.lessThan(minYieldFilled)) { + minYieldFilled = yield; + } else if (yield.greaterThan(maxYieldFilled)) { + maxYieldFilled = yield; + } - if (bids[i].couponExpiryEpoch < minExpiryFilled) { - minExpiryFilled = bids[i].couponExpiryEpoch; - } else if (bids[i].couponExpiryEpoch > maxExpiryFilled) { - maxExpiryFilled = bids[i].couponExpiryEpoch; - } - - sumYieldFilled += yield.asUint256(); - sumExpiryFilled += bids[i].couponExpiryEpoch; - totalAuctioned += bids[i].couponAmount; - totalBurned += bids[i].dollarAmount; - - uint256 epochExpiry = epoch().add(bids[i].couponExpiryEpoch); - burnFromAccount(bids[i].bidder, bids[i].dollarAmount); - incrementBalanceOfCoupons(bids[i].bidder, epochExpiry, bids[i].couponAmount); - setCouponBidderStateSelected(settlementEpoch, bids[i].bidder, i); - totalFilled++; - } else { - setCouponBidderStateRejected(settlementEpoch, bids[i].bidder); + if (bids[i].couponExpiryEpoch < minExpiryFilled) { + minExpiryFilled = bids[i].couponExpiryEpoch; + } else if (bids[i].couponExpiryEpoch > maxExpiryFilled) { + maxExpiryFilled = bids[i].couponExpiryEpoch; } + + sumYieldFilled += yield.asUint256(); + sumExpiryFilled += bids[i].couponExpiryEpoch; + totalAuctioned += bids[i].couponAmount; + totalBurned += bids[i].dollarAmount; + + uint256 epochExpiry = epoch().add(bids[i].couponExpiryEpoch); + burnFromAccount(bids[i].bidder, bids[i].dollarAmount); + incrementBalanceOfCoupons(bids[i].bidder, epochExpiry, bids[i].couponAmount); + setCouponBidderStateSelected(settlementEpoch, bids[i].bidder, i); + totalFilled++; + } else { + setCouponBidderStateRejected(settlementEpoch, bids[i].bidder); } - } else { - /* setCouponBidderStateRejected(settlementEpoch, bids[i].bidder); or just break and close the auction */ - break; - } + } + } // set auction internals @@ -307,7 +302,7 @@ contract Regulator is Comptroller { while (totalRedeemable() > 0) { bool willRedeemableOverflow = false; // loop over past epochs from the latest `dead` epoch to the current - for (uint256 d_idx = getLatestDeadAuctionEpoch(); d_idx < uint256(epoch()); d_idx++) { + for (uint256 d_idx = getEarliestDeadAuctionEpoch(); d_idx < uint256(epoch()); d_idx++) { uint256 temp_coupon_auction_epoch = d_idx; Epoch.AuctionState storage auction = getCouponAuctionAtEpoch(temp_coupon_auction_epoch); @@ -384,7 +379,7 @@ contract Regulator is Comptroller { // if all have been tried to be redeemd or expired, mark auction as `dead` if (totalCurrentlyTriedRedeemed == getTotalFilled(temp_coupon_auction_epoch)) { - setLatestDeadAuctionEpoch(temp_coupon_auction_epoch); + setEarliestDeadAuctionEpoch(temp_coupon_auction_epoch); setCouponAuctionStateDead(temp_coupon_auction_epoch); } } @@ -392,7 +387,7 @@ contract Regulator is Comptroller { } if (willRedeemableOverflow) { - // stop trying to redeem accross auctions + // stop trying to redeem across auctions break; } } diff --git a/protocol/contracts/dao/Setters.sol b/protocol/contracts/dao/Setters.sol index 58e85414..00e5b42a 100644 --- a/protocol/contracts/dao/Setters.sol +++ b/protocol/contracts/dao/Setters.sol @@ -278,8 +278,8 @@ contract Setters is State, Getters { _state.epochs[epoch].auction.totalBurned = totalBurned; } - function setLatestDeadAuctionEpoch(uint256 epoch) internal { - _state.epoch.latestDeadAuction = epoch; + function setEarliestDeadAuctionEpoch(uint256 epoch) internal { + _state.epoch.earliestDeadAuction = epoch; } function setLatestCouponAuctionRedeemedSelectedBidderIndex(uint256 epoch, uint256 index) internal { diff --git a/protocol/contracts/dao/State.sol b/protocol/contracts/dao/State.sol index 3707aba1..b3d8abe7 100644 --- a/protocol/contracts/dao/State.sol +++ b/protocol/contracts/dao/State.sol @@ -44,7 +44,7 @@ contract Epoch { uint256 start; uint256 period; uint256 current; - uint256 latestDeadAuction; + uint256 earliestDeadAuction; } struct Coupons { From a2358b46701484e261c31a4640e45f3a1a503d5c Mon Sep 17 00:00:00 2001 From: cinquemb <1519017+cinquemb@users.noreply.github.com> Date: Tue, 19 Jan 2021 10:01:47 -0500 Subject: [PATCH 43/47] dont use totalNet() for growSupply using dollar().totalSupply() --- protocol/contracts/dao/Regulator.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocol/contracts/dao/Regulator.sol b/protocol/contracts/dao/Regulator.sol index 2ba0b34d..b6bd9a98 100644 --- a/protocol/contracts/dao/Regulator.sol +++ b/protocol/contracts/dao/Regulator.sol @@ -88,7 +88,7 @@ contract Regulator is Comptroller { uint256 lessDebt = resetDebt(Decimal.zero()); Decimal.D256 memory delta = Decimal.ratio(Decimal.one(), getAvgAvgYieldAcrossCouponAuctions()); - uint256 newSupply = delta.mul(totalNet()).asUint256(); + uint256 newSupply = delta.mul(dollar().totalSupply()).asUint256(); (uint256 newRedeemable, uint256 newBonded) = increaseSupply(newSupply); emit SupplyIncrease(epoch(), price.value, newRedeemable, lessDebt, newBonded); } From 3427b39676541cfbcdecba8fe99b4ba985074bdb Mon Sep 17 00:00:00 2001 From: cinquemb <1519017+cinquemb@users.noreply.github.com> Date: Tue, 19 Jan 2021 10:07:57 -0500 Subject: [PATCH 44/47] bad conditional for not finished auction --- protocol/contracts/dao/Getters.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocol/contracts/dao/Getters.sol b/protocol/contracts/dao/Getters.sol index ead4decc..69cdc9d2 100644 --- a/protocol/contracts/dao/Getters.sol +++ b/protocol/contracts/dao/Getters.sol @@ -307,7 +307,7 @@ contract Getters is State { uint256 temp_coupon_auction_epoch = d_idx; Epoch.AuctionState storage auction = getCouponAuctionAtEpoch(temp_coupon_auction_epoch); // skip auctions that have been canceled, dead or not finished auction present? - if (!auction.canceled && !auction.dead && auction.isInit && !auction.finished) { + if (!auction.canceled && !auction.dead && auction.isInit && auction.finished) { sumYield += getAvgYieldFilled(temp_coupon_auction_epoch); totalAvailableAuctions++; } From 12f6f6bd6a3ae91f6aaf9bcb3678110263acf5b4 Mon Sep 17 00:00:00 2001 From: cinquemb <1519017+cinquemb@users.noreply.github.com> Date: Tue, 19 Jan 2021 10:27:14 -0500 Subject: [PATCH 45/47] remove debt requirement from placeCouponAuctionBid --- protocol/contracts/dao/Market.sol | 6 ------ protocol/contracts/mock/MockRegulator.sol | 6 ------ protocol/test/dao/Market.test.js | 8 +------- 3 files changed, 1 insertion(+), 19 deletions(-) diff --git a/protocol/contracts/dao/Market.sol b/protocol/contracts/dao/Market.sol index 3ddf75c7..f9668274 100644 --- a/protocol/contracts/dao/Market.sol +++ b/protocol/contracts/dao/Market.sol @@ -137,12 +137,6 @@ contract Market is Comptroller, Curve { FILE, "Must bid on non-zero amount" ); - - Require.that( - totalDebt() >= dollarAmount, - FILE, - "Not enough debt" - ); Require.that( acceptableBidCheck(msg.sender, dollarAmount), diff --git a/protocol/contracts/mock/MockRegulator.sol b/protocol/contracts/mock/MockRegulator.sol index daad48f5..b3844ed6 100644 --- a/protocol/contracts/mock/MockRegulator.sol +++ b/protocol/contracts/mock/MockRegulator.sol @@ -102,12 +102,6 @@ contract MockRegulator is MockComptroller, Regulator { FILE, "Must bid on non-zero amount" ); - - Require.that( - totalDebt() >= dollarAmount, - FILE, - "Not enough debt" - ); Require.that( acceptableBidCheck(msg.sender, dollarAmount), diff --git a/protocol/test/dao/Market.test.js b/protocol/test/dao/Market.test.js index db1aee2e..e75dd358 100644 --- a/protocol/test/dao/Market.test.js +++ b/protocol/test/dao/Market.test.js @@ -166,13 +166,7 @@ describe('Market', function () { await expectRevert(this.market.placeCouponAuctionBid(1, 1, 0, {from: userAddress}), "Market: Must bid on non-zero amount"); }); }); - - describe('no debt', function () { - it('reverts', async function () { - await expectRevert(this.market.placeCouponAuctionBid(1, 100000, 100000000), "Market: Not enough debt"); - }); - }); - + describe('on single call', function () { beforeEach(async function () { await this.market.incrementTotalDebtE(100000); From 95473b1dcb4505b04f9ad21e3fa6140e5a99fa02 Mon Sep 17 00:00:00 2001 From: cinquemb <1519017+cinquemb@users.noreply.github.com> Date: Wed, 20 Jan 2021 01:16:13 -0500 Subject: [PATCH 46/47] use diff burnFromAccount function --- protocol/contracts/dao/Comptroller.sol | 9 +++++++-- protocol/contracts/dao/Regulator.sol | 4 ++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/protocol/contracts/dao/Comptroller.sol b/protocol/contracts/dao/Comptroller.sol index 2b89baf5..796ad748 100644 --- a/protocol/contracts/dao/Comptroller.sol +++ b/protocol/contracts/dao/Comptroller.sol @@ -35,11 +35,16 @@ contract Comptroller is Setters { balanceCheck(); } + function burnFromAccountSansDebt(address account, uint256 amount) internal { + dollar().transferFrom(account, address(this), amount); + dollar().burn(amount); + balanceCheck(); + } + function burnFromAccount(address account, uint256 amount) internal { dollar().transferFrom(account, address(this), amount); dollar().burn(amount); - // debt doest matter - // decrementTotalDebt(amount, "Comptroller: not enough outstanding debt"); + decrementTotalDebt(amount, "Comptroller: not enough outstanding debt"); balanceCheck(); } diff --git a/protocol/contracts/dao/Regulator.sol b/protocol/contracts/dao/Regulator.sol index b6bd9a98..5adc9fa5 100644 --- a/protocol/contracts/dao/Regulator.sol +++ b/protocol/contracts/dao/Regulator.sol @@ -68,7 +68,7 @@ contract Regulator is Comptroller { finishCouponAuctionAtEpoch(epoch() - 1); } initCouponAuction(); - + shrinkSupply(price); return; } @@ -232,7 +232,7 @@ contract Regulator is Comptroller { totalBurned += bids[i].dollarAmount; uint256 epochExpiry = epoch().add(bids[i].couponExpiryEpoch); - burnFromAccount(bids[i].bidder, bids[i].dollarAmount); + burnFromAccountSansDebt(bids[i].bidder, bids[i].dollarAmount); incrementBalanceOfCoupons(bids[i].bidder, epochExpiry, bids[i].couponAmount); setCouponBidderStateSelected(settlementEpoch, bids[i].bidder, i); totalFilled++; From 88262b9ac0b83971b076788cb379c804f124834a Mon Sep 17 00:00:00 2001 From: cinquemb <1519017+cinquemb@users.noreply.github.com> Date: Wed, 20 Jan 2021 02:15:25 -0500 Subject: [PATCH 47/47] fixing ratio calc --- protocol/contracts/dao/Regulator.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocol/contracts/dao/Regulator.sol b/protocol/contracts/dao/Regulator.sol index 5adc9fa5..70ab2f13 100644 --- a/protocol/contracts/dao/Regulator.sol +++ b/protocol/contracts/dao/Regulator.sol @@ -87,7 +87,7 @@ contract Regulator is Comptroller { function growSupply(Decimal.D256 memory price) private { uint256 lessDebt = resetDebt(Decimal.zero()); - Decimal.D256 memory delta = Decimal.ratio(Decimal.one(), getAvgAvgYieldAcrossCouponAuctions()); + Decimal.D256 memory delta = Decimal.ratio(1, getAvgAvgYieldAcrossCouponAuctions()); uint256 newSupply = delta.mul(dollar().totalSupply()).asUint256(); (uint256 newRedeemable, uint256 newBonded) = increaseSupply(newSupply); emit SupplyIncrease(epoch(), price.value, newRedeemable, lessDebt, newBonded);