-
Notifications
You must be signed in to change notification settings - Fork 201
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(perp): Track prepaid bad debt (#451)
* Refactor imports * Add prepaid bad debt state * Add increment function and rename prepaidbaddebtstate * Default return zero when not found * Remove errors from PBD state * Add withdraw method * Fix lint errors
- Loading branch information
1 parent
0d45ffb
commit bcfb80a
Showing
5 changed files
with
253 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package keeper | ||
|
||
import ( | ||
"testing" | ||
|
||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestPrepaidBadDebtState(t *testing.T) { | ||
perpKeeper, _, ctx := getKeeper(t) | ||
|
||
t.Log("not found results in zero") | ||
amount := perpKeeper.PrepaidBadDebtState().Get(ctx, "foo") | ||
assert.EqualValues(t, sdk.ZeroInt(), amount) | ||
|
||
t.Log("set and get") | ||
perpKeeper.PrepaidBadDebtState().Set(ctx, "NUSD", sdk.NewInt(100)) | ||
|
||
amount = perpKeeper.PrepaidBadDebtState().Get(ctx, "NUSD") | ||
assert.EqualValues(t, sdk.NewInt(100), amount) | ||
|
||
t.Log("increment and check") | ||
amount = perpKeeper.PrepaidBadDebtState().Increment(ctx, "NUSD", sdk.NewInt(50)) | ||
assert.EqualValues(t, sdk.NewInt(150), amount) | ||
|
||
amount = perpKeeper.PrepaidBadDebtState().Get(ctx, "NUSD") | ||
assert.EqualValues(t, sdk.NewInt(150), amount) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
package keeper | ||
|
||
import ( | ||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
|
||
"github.com/NibiruChain/nibiru/x/perp/types" | ||
) | ||
|
||
/* | ||
Withdraws coins from the vault to the receiver. | ||
If the total amount of coins to withdraw is greater than the vault's amount, then | ||
withdraw the shortage from the PerpEF and mark it as prepaid bad debt. | ||
Prepaid bad debt will count towards realized bad debt from negative PnL positions | ||
when those are closed/liquidated. | ||
An example of this happening is when a long position has really high PnL and | ||
closes their position, realizing their profits. | ||
There is a counter party short position with really negative PnL, but | ||
their position hasn't been closed/liquidated yet. | ||
We must pay the long trader first, which results in funds being taken from the EF. | ||
When the short position is closed, it also realizes some bad debt but because | ||
we have already withdrawn from the EF, we don't need to withdraw more from the EF. | ||
*/ | ||
func (k Keeper) Withdraw( | ||
ctx sdk.Context, | ||
denom string, | ||
receiver sdk.AccAddress, | ||
amountToWithdraw sdk.Int, | ||
) (err error) { | ||
if !amountToWithdraw.IsPositive() { | ||
return nil | ||
} | ||
|
||
vaultQuoteBalance := k.BankKeeper.GetBalance( | ||
ctx, | ||
k.AccountKeeper.GetModuleAddress(types.VaultModuleAccount), | ||
denom, | ||
) | ||
if vaultQuoteBalance.Amount.LT(amountToWithdraw) { | ||
// if withdraw amount is larger than entire balance of vault | ||
// means this trader's profit comes from other under collateral position's future loss | ||
// and the balance of entire vault is not enough | ||
// need money from PerpEF to pay first, and record this prepaidBadDebt | ||
shortage := amountToWithdraw.Sub(vaultQuoteBalance.Amount) | ||
k.PrepaidBadDebtState().Increment(ctx, denom, shortage) | ||
if err := k.BankKeeper.SendCoinsFromModuleToModule( | ||
ctx, | ||
types.PerpEFModuleAccount, | ||
types.VaultModuleAccount, | ||
sdk.NewCoins( | ||
sdk.NewCoin(denom, shortage), | ||
), | ||
); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
// Transfer from Vault to receiver | ||
return k.BankKeeper.SendCoinsFromModuleToAccount( | ||
ctx, | ||
/* from */ types.VaultModuleAccount, | ||
/* to */ receiver, | ||
sdk.NewCoins( | ||
sdk.NewCoin(denom, amountToWithdraw), | ||
), | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
package keeper | ||
|
||
import ( | ||
"testing" | ||
|
||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" | ||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
|
||
"github.com/NibiruChain/nibiru/x/perp/types" | ||
"github.com/NibiruChain/nibiru/x/testutil/sample" | ||
) | ||
|
||
func TestWithdraw(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
initialVaultBalance int64 | ||
initialPrepaidBadDebt int64 | ||
amountToWithdraw int64 | ||
|
||
expectedPerpEFWithdrawal int64 | ||
expectedFinalPrepaidBadDebt int64 | ||
}{ | ||
{ | ||
name: "no bad debt", | ||
initialVaultBalance: 100, | ||
initialPrepaidBadDebt: 0, | ||
|
||
amountToWithdraw: 10, | ||
|
||
expectedPerpEFWithdrawal: 0, | ||
expectedFinalPrepaidBadDebt: 0, | ||
}, | ||
{ | ||
name: "creates prepaid bad debt", | ||
initialVaultBalance: 9, | ||
initialPrepaidBadDebt: 0, | ||
|
||
amountToWithdraw: 10, | ||
|
||
expectedPerpEFWithdrawal: 1, | ||
expectedFinalPrepaidBadDebt: 1, | ||
}, | ||
{ | ||
name: "increases existing prepaid bad debt", | ||
initialVaultBalance: 9, | ||
initialPrepaidBadDebt: 1, | ||
|
||
amountToWithdraw: 10, | ||
|
||
expectedPerpEFWithdrawal: 1, | ||
expectedFinalPrepaidBadDebt: 2, | ||
}, | ||
} | ||
|
||
for _, tc := range tests { | ||
tc := tc | ||
t.Run(tc.name, func(t *testing.T) { | ||
t.Log("initialize variables") | ||
perpKeeper, mocks, ctx := getKeeper(t) | ||
receiver := sample.AccAddress() | ||
denom := "NUSD" | ||
|
||
t.Log("mock account keeper") | ||
vaultAddr := authtypes.NewModuleAddress(types.VaultModuleAccount) | ||
mocks.mockAccountKeeper.EXPECT().GetModuleAddress( | ||
types.VaultModuleAccount). | ||
Return(vaultAddr) | ||
|
||
t.Log("mock bank keeper") | ||
mocks.mockBankKeeper.EXPECT().GetBalance(ctx, vaultAddr, denom). | ||
Return(sdk.NewInt64Coin(denom, tc.initialVaultBalance)) | ||
mocks.mockBankKeeper.EXPECT().SendCoinsFromModuleToAccount( | ||
ctx, types.VaultModuleAccount, receiver, | ||
sdk.NewCoins(sdk.NewInt64Coin(denom, tc.amountToWithdraw)), | ||
).Return(nil) | ||
if tc.expectedPerpEFWithdrawal > 0 { | ||
mocks.mockBankKeeper.EXPECT().SendCoinsFromModuleToModule( | ||
ctx, types.PerpEFModuleAccount, types.VaultModuleAccount, | ||
sdk.NewCoins(sdk.NewInt64Coin(denom, tc.expectedPerpEFWithdrawal)), | ||
).Return(nil) | ||
} | ||
|
||
t.Log("initial prepaid bad debt") | ||
perpKeeper.PrepaidBadDebtState().Set(ctx, denom, sdk.NewInt(tc.initialPrepaidBadDebt)) | ||
|
||
t.Log("execute withdrawal") | ||
err := perpKeeper.Withdraw(ctx, denom, receiver, sdk.NewInt(tc.amountToWithdraw)) | ||
require.NoError(t, err) | ||
|
||
t.Log("assert new prepaid bad debt") | ||
prepaidBadDebt := perpKeeper.PrepaidBadDebtState().Get(ctx, denom) | ||
assert.EqualValues(t, tc.expectedFinalPrepaidBadDebt, prepaidBadDebt.Int64()) | ||
}) | ||
} | ||
} |