add debt tracking to auctions

This commit is contained in:
rhuairahrighairigh 2020-01-12 15:17:47 +01:00
parent 27f3e76da3
commit 61e5de556c
5 changed files with 96 additions and 34 deletions

View File

@ -25,7 +25,7 @@ func TestKeeper_EndBlocker(t *testing.T) {
tApp := app.NewTestApp() tApp := app.NewTestApp()
sellerAcc := supply.NewEmptyModuleAccount(sellerModName) sellerAcc := supply.NewEmptyModuleAccount(sellerModName)
require.NoError(t, sellerAcc.SetCoins(cs(c("token1", 100), c("token2", 100)))) require.NoError(t, sellerAcc.SetCoins(cs(c("token1", 100), c("token2", 100), c("debt", 100))))
tApp.InitializeFromGenesisStates( tApp.InitializeFromGenesisStates(
NewAuthGenStateFromAccs(authexported.GenesisAccounts{ NewAuthGenStateFromAccs(authexported.GenesisAccounts{
auth.NewBaseAccount(buyer, cs(c("token1", 100), c("token2", 100)), nil, 0, 0), auth.NewBaseAccount(buyer, cs(c("token1", 100), c("token2", 100)), nil, 0, 0),
@ -36,7 +36,7 @@ func TestKeeper_EndBlocker(t *testing.T) {
ctx := tApp.NewContext(true, abci.Header{}) ctx := tApp.NewContext(true, abci.Header{})
keeper := tApp.GetAuctionKeeper() keeper := tApp.GetAuctionKeeper()
auctionID, err := keeper.StartCollateralAuction(ctx, sellerModName, c("token1", 20), c("token2", 50), returnAddrs, returnWeights) auctionID, err := keeper.StartCollateralAuction(ctx, sellerModName, c("token1", 20), c("token2", 50), returnAddrs, returnWeights, c("debt", 40))
require.NoError(t, err) require.NoError(t, err)
require.NoError(t, keeper.PlaceBid(ctx, auctionID, buyer, c("token2", 30))) require.NoError(t, keeper.PlaceBid(ctx, auctionID, buyer, c("token2", 30)))

View File

@ -31,13 +31,14 @@ func (k Keeper) StartSurplusAuction(ctx sdk.Context, seller string, lot sdk.Coin
} }
// StartDebtAuction starts a new debt (reverse) auction. // StartDebtAuction starts a new debt (reverse) auction.
func (k Keeper) StartDebtAuction(ctx sdk.Context, buyer string, bid sdk.Coin, initialLot sdk.Coin) (uint64, sdk.Error) { func (k Keeper) StartDebtAuction(ctx sdk.Context, buyer string, bid sdk.Coin, initialLot sdk.Coin, debt sdk.Coin) (uint64, sdk.Error) {
auction := types.NewDebtAuction( auction := types.NewDebtAuction(
buyer, buyer,
bid, bid,
initialLot, initialLot,
ctx.BlockTime().Add(k.GetParams(ctx).MaxAuctionDuration)) ctx.BlockTime().Add(k.GetParams(ctx).MaxAuctionDuration),
debt)
// This auction type mints coins at close. Need to check module account has minting privileges to avoid potential err in endblocker. // This auction type mints coins at close. Need to check module account has minting privileges to avoid potential err in endblocker.
macc := k.supplyKeeper.GetModuleAccount(ctx, buyer) macc := k.supplyKeeper.GetModuleAccount(ctx, buyer)
@ -45,6 +46,11 @@ func (k Keeper) StartDebtAuction(ctx sdk.Context, buyer string, bid sdk.Coin, in
return 0, sdk.ErrInternal("module does not have minting permissions") return 0, sdk.ErrInternal("module does not have minting permissions")
} }
err := k.supplyKeeper.SendCoinsFromModuleToModule(ctx, buyer, types.ModuleName, sdk.NewCoins(debt))
if err != nil {
return 0, err
}
auctionID, err := k.StoreNewAuction(ctx, auction) auctionID, err := k.StoreNewAuction(ctx, auction)
if err != nil { if err != nil {
return 0, err return 0, err
@ -53,18 +59,28 @@ func (k Keeper) StartDebtAuction(ctx sdk.Context, buyer string, bid sdk.Coin, in
} }
// StartCollateralAuction starts a new collateral (2-phase) auction. // StartCollateralAuction starts a new collateral (2-phase) auction.
func (k Keeper) StartCollateralAuction(ctx sdk.Context, seller string, lot sdk.Coin, maxBid sdk.Coin, lotReturnAddrs []sdk.AccAddress, lotReturnWeights []sdk.Int) (uint64, sdk.Error) { func (k Keeper) StartCollateralAuction(ctx sdk.Context, seller string, lot sdk.Coin, maxBid sdk.Coin, lotReturnAddrs []sdk.AccAddress, lotReturnWeights []sdk.Int, debt sdk.Coin) (uint64, sdk.Error) {
weightedAddresses, err := types.NewWeightedAddresses(lotReturnAddrs, lotReturnWeights) weightedAddresses, err := types.NewWeightedAddresses(lotReturnAddrs, lotReturnWeights)
if err != nil { if err != nil {
return 0, err return 0, err
} }
auction := types.NewCollateralAuction(seller, lot, ctx.BlockTime().Add(types.DefaultMaxAuctionDuration), maxBid, weightedAddresses) auction := types.NewCollateralAuction(
seller,
lot,
ctx.BlockTime().Add(types.DefaultMaxAuctionDuration),
maxBid,
weightedAddresses,
debt)
err = k.supplyKeeper.SendCoinsFromModuleToModule(ctx, seller, types.ModuleName, sdk.NewCoins(lot)) err = k.supplyKeeper.SendCoinsFromModuleToModule(ctx, seller, types.ModuleName, sdk.NewCoins(lot))
if err != nil { if err != nil {
return 0, err return 0, err
} }
err = k.supplyKeeper.SendCoinsFromModuleToModule(ctx, seller, types.ModuleName, sdk.NewCoins(debt))
if err != nil {
return 0, err
}
auctionID, err := k.StoreNewAuction(ctx, auction) auctionID, err := k.StoreNewAuction(ctx, auction)
if err != nil { if err != nil {
@ -184,10 +200,24 @@ func (k Keeper) PlaceForwardBidCollateral(ctx sdk.Context, a types.CollateralAuc
} }
} }
// Increase in bid sent to auction initiator // Increase in bid sent to auction initiator
err := k.supplyKeeper.SendCoinsFromAccountToModule(ctx, bidder, a.Initiator, sdk.NewCoins(bid.Sub(a.Bid))) bidIncrement := bid.Sub(a.Bid)
err := k.supplyKeeper.SendCoinsFromAccountToModule(ctx, bidder, a.Initiator, sdk.NewCoins(bidIncrement))
if err != nil { if err != nil {
return a, err return a, err
} }
// Debt coins are sent to liquidator (until there is no CorrespondingDebt left). Amount sent is equal to bidIncrement.
if a.CorrespondingDebt.IsPositive() {
debtAmountToReturn := sdk.MinInt(bidIncrement.Amount, a.CorrespondingDebt.Amount)
debtToReturn := sdk.NewCoin(a.CorrespondingDebt.Denom, debtAmountToReturn)
err = k.supplyKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, a.Initiator, sdk.NewCoins(debtToReturn))
if err != nil {
return a, err
}
a.CorrespondingDebt = a.CorrespondingDebt.Sub(debtToReturn) // debtToReturn will always be ≤ a.CorrespondingDebt from the MinInt above
// TODO optionally burn out debt and stable just returned to liquidator
}
// Update Auction // Update Auction
a.Bidder = bidder a.Bidder = bidder
@ -271,6 +301,19 @@ func (k Keeper) PlaceBidDebt(ctx sdk.Context, a types.DebtAuction, bidder sdk.Ac
return a, err return a, err
} }
} }
// Debt coins are sent to liquidator the first time a bid is placed. Amount sent is equal to Bid.
if a.Bidder.Equals(supply.NewModuleAddress(a.Initiator)) {
debtAmountToReturn := sdk.MinInt(a.Bid.Amount, a.CorrespondingDebt.Amount)
debtToReturn := sdk.NewCoin(a.CorrespondingDebt.Denom, debtAmountToReturn)
err := k.supplyKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, a.Initiator, sdk.NewCoins(debtToReturn))
if err != nil {
return a, err
}
a.CorrespondingDebt = a.CorrespondingDebt.Sub(debtToReturn) // debtToReturn will always be ≤ a.CorrespondingDebt from the MinInt above
// TODO optionally burn out debt and stable just returned to liquidator
}
// Update Auction // Update Auction
a.Bidder = bidder a.Bidder = bidder
@ -324,6 +367,12 @@ func (k Keeper) PayoutDebtAuction(ctx sdk.Context, a types.DebtAuction) sdk.Erro
if err != nil { if err != nil {
return err return err
} }
if a.CorrespondingDebt.IsPositive() {
err = k.supplyKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, a.Initiator, sdk.NewCoins(a.CorrespondingDebt))
if err != nil {
return err
}
}
return nil return nil
} }
@ -342,6 +391,12 @@ func (k Keeper) PayoutCollateralAuction(ctx sdk.Context, a types.CollateralAucti
if err != nil { if err != nil {
return err return err
} }
if a.CorrespondingDebt.IsPositive() {
err = k.supplyKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, a.Initiator, sdk.NewCoins(a.CorrespondingDebt))
if err != nil {
return err
}
}
return nil return nil
} }

View File

@ -69,27 +69,29 @@ func TestDebtAuctionBasic(t *testing.T) {
tApp := app.NewTestApp() tApp := app.NewTestApp()
buyerAcc := supply.NewEmptyModuleAccount(buyerModName, supply.Minter) // reverse auctions mint payout
require.NoError(t, buyerAcc.SetCoins(cs(c("debt", 100))))
tApp.InitializeFromGenesisStates( tApp.InitializeFromGenesisStates(
NewAuthGenStateFromAccs(authexported.GenesisAccounts{ NewAuthGenStateFromAccs(authexported.GenesisAccounts{
auth.NewBaseAccount(seller, cs(c("token1", 100), c("token2", 100)), nil, 0, 0), auth.NewBaseAccount(seller, cs(c("token1", 100), c("token2", 100)), nil, 0, 0),
supply.NewEmptyModuleAccount(buyerModName, supply.Minter), // reverse auctions mint payout buyerAcc,
}), }),
) )
ctx := tApp.NewContext(false, abci.Header{}) ctx := tApp.NewContext(false, abci.Header{})
keeper := tApp.GetAuctionKeeper() keeper := tApp.GetAuctionKeeper()
// Start auction // Start auction
auctionID, err := keeper.StartDebtAuction(ctx, buyerModName, c("token1", 20), c("token2", 99999)) // buyer, bid, initialLot auctionID, err := keeper.StartDebtAuction(ctx, buyerModName, c("token1", 20), c("token2", 99999), c("debt", 20))
require.NoError(t, err) require.NoError(t, err)
// Check buyer's coins have not decreased, as lot is minted at the end // Check buyer's coins have not decreased (except for debt), as lot is minted at the end
tApp.CheckBalance(t, ctx, buyerAddr, nil) // zero coins tApp.CheckBalance(t, ctx, buyerAddr, cs(c("debt", 80)))
// Place a bid // Place a bid
require.NoError(t, keeper.PlaceBid(ctx, 0, seller, c("token2", 10))) require.NoError(t, keeper.PlaceBid(ctx, 0, seller, c("token2", 10)))
// Check seller's coins have decreased // Check seller's coins have decreased
tApp.CheckBalance(t, ctx, seller, cs(c("token1", 80), c("token2", 100))) tApp.CheckBalance(t, ctx, seller, cs(c("token1", 80), c("token2", 100)))
// Check buyer's coins have increased // Check buyer's coins have increased
tApp.CheckBalance(t, ctx, buyerAddr, cs(c("token1", 20))) tApp.CheckBalance(t, ctx, buyerAddr, cs(c("token1", 20), c("debt", 100)))
// Close auction at just after auction expiry // Close auction at just after auction expiry
ctx = ctx.WithBlockTime(ctx.BlockTime().Add(types.DefaultBidDuration)) ctx = ctx.WithBlockTime(ctx.BlockTime().Add(types.DefaultBidDuration))
@ -109,7 +111,7 @@ func TestCollateralAuctionBasic(t *testing.T) {
tApp := app.NewTestApp() tApp := app.NewTestApp()
sellerAcc := supply.NewEmptyModuleAccount(sellerModName) sellerAcc := supply.NewEmptyModuleAccount(sellerModName)
require.NoError(t, sellerAcc.SetCoins(cs(c("token1", 100), c("token2", 100)))) require.NoError(t, sellerAcc.SetCoins(cs(c("token1", 100), c("token2", 100), c("debt", 100))))
tApp.InitializeFromGenesisStates( tApp.InitializeFromGenesisStates(
NewAuthGenStateFromAccs(authexported.GenesisAccounts{ NewAuthGenStateFromAccs(authexported.GenesisAccounts{
auth.NewBaseAccount(buyer, cs(c("token1", 100), c("token2", 100)), nil, 0, 0), auth.NewBaseAccount(buyer, cs(c("token1", 100), c("token2", 100)), nil, 0, 0),
@ -123,17 +125,17 @@ func TestCollateralAuctionBasic(t *testing.T) {
keeper := tApp.GetAuctionKeeper() keeper := tApp.GetAuctionKeeper()
// Start auction // Start auction
auctionID, err := keeper.StartCollateralAuction(ctx, sellerModName, c("token1", 20), c("token2", 50), returnAddrs, returnWeights) // seller, lot, maxBid, otherPerson auctionID, err := keeper.StartCollateralAuction(ctx, sellerModName, c("token1", 20), c("token2", 50), returnAddrs, returnWeights, c("debt", 40))
require.NoError(t, err) require.NoError(t, err)
// Check seller's coins have decreased // Check seller's coins have decreased
tApp.CheckBalance(t, ctx, sellerAddr, cs(c("token1", 80), c("token2", 100))) tApp.CheckBalance(t, ctx, sellerAddr, cs(c("token1", 80), c("token2", 100), c("debt", 60)))
// Place a forward bid // Place a forward bid
require.NoError(t, keeper.PlaceBid(ctx, 0, buyer, c("token2", 10))) require.NoError(t, keeper.PlaceBid(ctx, 0, buyer, c("token2", 10)))
// Check bidder's coins have decreased // Check bidder's coins have decreased
tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 100), c("token2", 90))) tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 100), c("token2", 90)))
// Check seller's coins have increased // Check seller's coins have increased
tApp.CheckBalance(t, ctx, sellerAddr, cs(c("token1", 80), c("token2", 110))) tApp.CheckBalance(t, ctx, sellerAddr, cs(c("token1", 80), c("token2", 110), c("debt", 70)))
// Check return addresses have not received coins // Check return addresses have not received coins
for _, ra := range returnAddrs { for _, ra := range returnAddrs {
tApp.CheckBalance(t, ctx, ra, cs(c("token1", 100), c("token2", 100))) tApp.CheckBalance(t, ctx, ra, cs(c("token1", 100), c("token2", 100)))
@ -145,7 +147,7 @@ func TestCollateralAuctionBasic(t *testing.T) {
// Check bidder's coins have decreased // Check bidder's coins have decreased
tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 100), c("token2", 50))) tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 100), c("token2", 50)))
// Check seller's coins have increased // Check seller's coins have increased
tApp.CheckBalance(t, ctx, sellerAddr, cs(c("token1", 80), c("token2", 150))) tApp.CheckBalance(t, ctx, sellerAddr, cs(c("token1", 80), c("token2", 150), c("debt", 100)))
// Check return addresses have received coins // Check return addresses have received coins
tApp.CheckBalance(t, ctx, returnAddrs[0], cs(c("token1", 102), c("token2", 100))) tApp.CheckBalance(t, ctx, returnAddrs[0], cs(c("token1", 102), c("token2", 100)))
tApp.CheckBalance(t, ctx, returnAddrs[1], cs(c("token1", 102), c("token2", 100))) tApp.CheckBalance(t, ctx, returnAddrs[1], cs(c("token1", 102), c("token2", 100)))

View File

@ -74,8 +74,8 @@ func TestIterateAuctions(t *testing.T) {
auctions := []types.Auction{ auctions := []types.Auction{
types.NewSurplusAuction("sellerMod", c("denom", 12345678), "anotherdenom", time.Date(1998, time.January, 1, 0, 0, 0, 0, time.UTC)).WithID(0), types.NewSurplusAuction("sellerMod", c("denom", 12345678), "anotherdenom", time.Date(1998, time.January, 1, 0, 0, 0, 0, time.UTC)).WithID(0),
types.NewDebtAuction("buyerMod", c("denom", 12345678), c("anotherdenom", 12345678), time.Date(1998, time.January, 1, 0, 0, 0, 0, time.UTC)).WithID(1), types.NewDebtAuction("buyerMod", c("denom", 12345678), c("anotherdenom", 12345678), time.Date(1998, time.January, 1, 0, 0, 0, 0, time.UTC), c("debt", 12345678)).WithID(1),
types.NewCollateralAuction("sellerMod", c("denom", 12345678), time.Date(1998, time.January, 1, 0, 0, 0, 0, time.UTC), c("anotherdenom", 12345678), types.WeightedAddresses{}).WithID(2), types.NewCollateralAuction("sellerMod", c("denom", 12345678), time.Date(1998, time.January, 1, 0, 0, 0, 0, time.UTC), c("anotherdenom", 12345678), types.WeightedAddresses{}, c("debt", 12345678)).WithID(2),
} }
for _, a := range auctions { for _, a := range auctions {
keeper.SetAuction(ctx, a) keeper.SetAuction(ctx, a)

View File

@ -73,25 +73,28 @@ func NewSurplusAuction(seller string, lot sdk.Coin, bidDenom string, endTime tim
// It is normally used to acquire pegged asset to cover the CDP system's debts that were not covered by selling collateral. // It is normally used to acquire pegged asset to cover the CDP system's debts that were not covered by selling collateral.
type DebtAuction struct { type DebtAuction struct {
BaseAuction BaseAuction
CorrespondingDebt sdk.Coin
} }
// WithID returns an auction with the ID set. // WithID returns an auction with the ID set.
func (a DebtAuction) WithID(id uint64) Auction { a.ID = id; return a } func (a DebtAuction) WithID(id uint64) Auction { a.ID = id; return a }
// NewDebtAuction returns a new debt auction. // NewDebtAuction returns a new debt auction.
func NewDebtAuction(buyerModAccName string, bid sdk.Coin, initialLot sdk.Coin, EndTime time.Time) DebtAuction { func NewDebtAuction(buyerModAccName string, bid sdk.Coin, initialLot sdk.Coin, EndTime time.Time, debt sdk.Coin) DebtAuction {
// Note: Bidder is set to the initiator's module account address instead of module name. (when the first bid is placed, it is paid out to the initiator) // Note: Bidder is set to the initiator's module account address instead of module name. (when the first bid is placed, it is paid out to the initiator)
// Setting to the module account address bypasses calling supply.SendCoinsFromModuleToModule, instead calls SendCoinsFromModuleToAccount. // Setting to the module account address bypasses calling supply.SendCoinsFromModuleToModule, instead calls SendCoinsFromModuleToAccount.
// This isn't a problem currently, but if additional logic/validation was added for sending to coins to Module Accounts, it would be bypassed. // This isn't a problem currently, but if additional logic/validation was added for sending to coins to Module Accounts, it would be bypassed.
auction := DebtAuction{BaseAuction{ auction := DebtAuction{
BaseAuction: BaseAuction{
// no ID // no ID
Initiator: buyerModAccName, Initiator: buyerModAccName,
Lot: initialLot, Lot: initialLot,
Bidder: supply.NewModuleAddress(buyerModAccName), // send proceeds from the first bid to the buyer. Bidder: supply.NewModuleAddress(buyerModAccName), // send proceeds from the first bid to the buyer.
Bid: bid, // amount that the buyer is buying - doesn't change over course of auction Bid: bid, // amount that the buyer is buying - doesn't change over course of auction
EndTime: EndTime, EndTime: EndTime,
MaxEndTime: EndTime, MaxEndTime: EndTime},
}} CorrespondingDebt: debt,
}
return auction return auction
} }
@ -102,6 +105,7 @@ func NewDebtAuction(buyerModAccName string, bid sdk.Coin, initialLot sdk.Coin, E
// Collateral auctions are normally used to sell off collateral seized from CDPs. // Collateral auctions are normally used to sell off collateral seized from CDPs.
type CollateralAuction struct { type CollateralAuction struct {
BaseAuction BaseAuction
CorrespondingDebt sdk.Coin
MaxBid sdk.Coin MaxBid sdk.Coin
LotReturns WeightedAddresses LotReturns WeightedAddresses
} }
@ -132,7 +136,7 @@ func (a CollateralAuction) String() string {
} }
// NewCollateralAuction returns a new collateral auction. // NewCollateralAuction returns a new collateral auction.
func NewCollateralAuction(seller string, lot sdk.Coin, EndTime time.Time, maxBid sdk.Coin, lotReturns WeightedAddresses) CollateralAuction { func NewCollateralAuction(seller string, lot sdk.Coin, EndTime time.Time, maxBid sdk.Coin, lotReturns WeightedAddresses, debt sdk.Coin) CollateralAuction {
auction := CollateralAuction{ auction := CollateralAuction{
BaseAuction: BaseAuction{ BaseAuction: BaseAuction{
// no ID // no ID
@ -142,6 +146,7 @@ func NewCollateralAuction(seller string, lot sdk.Coin, EndTime time.Time, maxBid
Bid: sdk.NewInt64Coin(maxBid.Denom, 0), Bid: sdk.NewInt64Coin(maxBid.Denom, 0),
EndTime: EndTime, EndTime: EndTime,
MaxEndTime: EndTime}, MaxEndTime: EndTime},
CorrespondingDebt: debt,
MaxBid: maxBid, MaxBid: maxBid,
LotReturns: lotReturns, LotReturns: lotReturns,
} }