Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Proposal] 17: User Defined Coupon Expiry w/ FPSBA #16

Open
wants to merge 47 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
cbde5d8
first shot at premium decay of coupon with longer expiry
cinquemb Dec 30, 2020
6c19be4
updates for eulers number and missing import
cinquemb Dec 31, 2020
4b19843
fix multiplier
cinquemb Dec 31, 2020
42de4ef
fix uint256 assignment
cinquemb Dec 31, 2020
1a4b15f
first stab at coupon auction
cinquemb Jan 3, 2021
f41d958
sort bids by euclidan distance with equal weights on min bid and min …
cinquemb Jan 3, 2021
5f7ab94
fix: sort on distance not on struct
cinquemb Jan 3, 2021
482e236
add coupoun assignment/selected and rejected fix yeild calc
cinquemb Jan 3, 2021
7569fee
remove unused imports and constants
cinquemb Jan 3, 2021
ba31c11
dont subtract off constant epoch when assignment
cinquemb Jan 3, 2021
e53a3c8
fixing some compiler errors
cinquemb Jan 3, 2021
22a8e74
fixing more compiler errors
cinquemb Jan 3, 2021
24f30f8
first pass with implementing FPSBA
cinquemb Jan 4, 2021
5837ecc
changing contract inheritance of Auction
cinquemb Jan 4, 2021
3b58701
starting to add mocks and tests
cinquemb Jan 4, 2021
1343eab
adding test conditions scaffoled for auction
cinquemb Jan 4, 2021
ea81de4
adding tests for placeCouponAuctionBid
cinquemb Jan 5, 2021
827157b
updating state mocks, incorperating dollar amount in euclidian distan…
cinquemb Jan 5, 2021
7161284
adding some auction internals
cinquemb Jan 5, 2021
f42edee
tight packing auction structs
cinquemb Jan 5, 2021
c9c6bc4
adding modifier so that only dao can settle auction
cinquemb Jan 6, 2021
3afd5f4
adding new state tests
cinquemb Jan 6, 2021
f16f5d4
fixing market test
cinquemb Jan 6, 2021
ae3ce9b
fixing stuff with market tests, working on auction tests
cinquemb Jan 7, 2021
d7d6965
finsihed with auction test
cinquemb Jan 7, 2021
d48d484
need to finish/cancel prev auction in Regulator.step
cinquemb Jan 7, 2021
d4fd56b
use actual uint256 max for min initializations
cinquemb Jan 7, 2021
cd29d39
fixing coupon auction internals
cinquemb Jan 8, 2021
8b47444
used auction factory to avoid high gas for regulator contract
cinquemb Jan 8, 2021
9d667b4
merged auction settlement stuff into regulator to bypass gas issues, …
cinquemb Jan 8, 2021
ac9628e
removing unneed files, more work on regulator tests
cinquemb Jan 8, 2021
811fdae
update regulator tests
cinquemb Jan 9, 2021
5afc125
done adding tests
cinquemb Jan 9, 2021
abe2465
add back in commented out test
cinquemb Jan 9, 2021
45db6c8
more parity between auction updates between dsd-esd
cinquemb Jan 16, 2021
faac8a0
state parity
cinquemb Jan 16, 2021
b29314e
state parity
cinquemb Jan 16, 2021
3476961
state parity
cinquemb Jan 16, 2021
e3354f3
fixing regulator tests
cinquemb Jan 16, 2021
cb2bc50
adding back in autoredemption, prioritizing cross auction best bidder…
cinquemb Jan 16, 2021
9806393
some comment stuff
cinquemb Jan 16, 2021
0fb939e
dont limit auction bids based on debt, grow supply based on auction i…
cinquemb Jan 16, 2021
a2358b4
dont use totalNet() for growSupply using dollar().totalSupply()
cinquemb Jan 19, 2021
3427b39
bad conditional for not finished auction
cinquemb Jan 19, 2021
12f6f6b
remove debt requirement from placeCouponAuctionBid
cinquemb Jan 19, 2021
95473b1
use diff burnFromAccount function
cinquemb Jan 20, 2021
88262b9
fixing ratio calc
cinquemb Jan 20, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 121 additions & 0 deletions protocol/contracts/dao/Auction.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
Copyright 2020 Empty Set Squad <[email protected]>

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;

Epoch.CouponBidderState[] private bids;

event AuctionCouponPurchase(address indexed account, uint256 indexed epoch, uint256 dollarAmount, uint256 couponAmount);

function sortBidsByDistance(Epoch.CouponBidderState[] memory bids) internal returns(Epoch.CouponBidderState[] memory) {
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)].distance;
while (i <= 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)]);
i++;
j--;
}
}
if (left < j)
quickSort(arr, left, j);
if (i < right)
quickSort(arr, i, right);
}

function sqrt(uint256 x) internal pure 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 settleCouponAuction() public returns (bool success) {
if (!isCouponAuctionFinished() && !isCouponAuctionCanceled()) {

uint256 minMaturity = getCouponAuctionMinMaturity();
uint256 maxMaturity = getCouponAuctionMaxMaturity();
uint256 minYield = getCouponAuctionMinYield();
uint256 maxYield = getCouponAuctionMaxYield();

// loop over bids and compute distance
for (uint256 i = 0; i < getCouponAuctionBids(); i++) {
uint256 couponMaturityEpoch = getCouponBidderState(getCouponBidderStateIndex(i)).couponMaturityEpoch;
uint256 couponAmount = getCouponBidderState(getCouponBidderStateIndex(i)).couponAmount;
uint256 dollarAmount = getCouponBidderState(getCouponBidderStateIndex(i)).dollarAmount;

uint256 yieldRel = couponAmount.div(
dollarAmount
).div(
maxYield.sub(minYield)
);
uint256 maturityRel = couponMaturityEpoch.div(
maxMaturity.sub(minMaturity)
);

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)));
}

// 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);
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);
}
} else {
setCouponBidderStateRejected(bids[i].bidder);
}
}
return true;
} else {
return true;
}
}
}
48 changes: 48 additions & 0 deletions protocol/contracts/dao/Getters.sol
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,54 @@ contract Getters is State {
return epoch <= Constants.getBootstrappingPeriod();
}

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;
}

function getCouponBidderState(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 getCouponBidderStateRejected(address bidder) internal view returns (bool) {
return _state.epochs[epoch()].auction.couponBidderState[bidder].rejected;
}

function getCouponBidderStateIndex(uint256 index) internal view returns (address) {
return _state.epochs[epoch()].auction.couponBidder[index];
}

function isCouponAuctionFinished() internal view returns (bool){
return _state.epochs[epoch()].auction.finished;
}

function isCouponAuctionCanceled() internal view returns (bool){
return _state.epochs[epoch()].auction.canceled;
}

function getCouponAuctionMinMaturity() internal view returns (uint256) {
return _state.epochs[epoch()].auction.minMaturity;
}

function getCouponAuctionMaxMaturity() internal view returns (uint256) {
return _state.epochs[epoch()].auction.maxMaturity;
}

function getCouponAuctionMinYield() internal view returns (uint256) {
return _state.epochs[epoch()].auction.minYield;
}

function getCouponAuctionMaxYield() internal view returns (uint256) {
return _state.epochs[epoch()].auction.maxYield;
}

/**
* Governance
*/
Expand Down
38 changes: 37 additions & 1 deletion protocol/contracts/dao/Market.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ 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
for (uint256 i = 0; i < expiringCoupons(epoch()); i++) {
Expand Down Expand Up @@ -117,4 +118,39 @@ contract Market is Comptroller, Curve {

emit CouponTransfer(sender, recipient, epoch, amount);
}

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));
setCouponAuctionRelMaturity(epoch);
setCouponBidderState(msg.sender, epoch, dollarAmount, maxCouponAmount);
setCouponBidderStateIndex(getCouponAuctionBids(), msg.sender);
incrementCouponAuctionBids();
emit CouponBidPlaced(msg.sender, epoch, dollarAmount, maxCouponAmount);
return true;
}
}
19 changes: 19 additions & 0 deletions protocol/contracts/dao/Regulator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand All @@ -34,11 +35,29 @@ contract Regulator is Comptroller {
Decimal.D256 memory price = oracleCapture();

if (price.greaterThan(Decimal.one())) {
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())) {
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;
}
Expand Down
58 changes: 58 additions & 0 deletions protocol/contracts/dao/Setters.sol
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,64 @@ contract Setters is State, Getters {
_state.epochs[epoch].coupons.outstanding = 0;
}

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.minYield = 1000000000000000000000000;
_state.epochs[epoch()].auction.maxYield = 0;
}

function cancelCouponAuctionAtEpoch(uint256 epoch) internal {
_state.epochs[epoch].auction.canceled = true;
}

function finishCouponAuctionAtEpoch(uint256 epoch) internal {
_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];

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 {
_state.epochs[epoch()].auction.couponBidder[index] = bidder;
}

function incrementCouponAuctionBids() internal {
_state.epochs[epoch()].auction._totalBids++;
}

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 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;
}
}


/**
* Governance
*/
Expand Down
26 changes: 25 additions & 1 deletion protocol/contracts/dao/State.sol
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,34 @@ contract Epoch {
uint256[] expiring;
}

struct CouponBidderState {
uint256 couponMaturityEpoch;
uint256 dollarAmount;
uint256 couponAmount;
uint256 distance;
address bidder;
bool selected;
bool rejected;
}

struct AuctionState {
bool canceled;
bool finished;
uint256 minMaturity;
uint256 maxMaturity;
uint256 minYield;
uint256 maxYield;
uint256 _totalBids;
mapping(uint256 => address) couponBidder;
mapping(address => CouponBidderState) couponBidderState;
address couponAuction;
}

struct State {
uint256 bonded;
Coupons coupons;
}
AuctionState auction;
}
}

contract Candidate {
Expand Down
29 changes: 29 additions & 0 deletions protocol/contracts/mock/MockAuction.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
Copyright 2020 Empty Set Squad <[email protected]>

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();
}
}
Loading