-
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): distribute liquidate rewards keeper helper method and Liq…
…uidationResp proto (#449) * feat: liquidate proto changes, new params, and new methods on perp interfaces * fix: restore passing state * refactor: coalesce errors to one location * feat (liquidate.go): ExecuteFullLiquidation, distributeLiquidateRewards * test: Check expected fee to liquidator * test (liquidate_test.go): turn tests green with expectedPerpEFBalance * test: Test_distributeLiquidateRewards * typo correction * typo correction * refactor: replace panic(err) with require.NoError * fix: ExecuteFullLiquidation * feat (perp): Emit internal events when positionResp objects are returned * linter * fix, test: perp.go and margin.go tests pass again * fix: settleposition test restored * fix: calc_test.go, calc_unit_test.go * test: liquidate_unit_test passing * fix, refactor: passing margin_test, liquidate_test * fix (clearing_house_test.go): Margin and MarginToVault should be sdk.Int, not sdk.Dec * test, docs (liquidate_test.go): Check correctness of emitted events. Add docs for calculations * refactor: require.EqualValues -> assert.EqualValues + more docs * docs: small decription * refactor: universal sdk.Decs * verify event calls * refactor: consistency b/w assert and require * refactor: rename CalcFee -> CalcPerpTxFee * refactor: rename CalcFee -> CalcPerpTxFee * refactor: Liquidate (0/4) - asserts, String() calls, and new params * refactor: clean up old TODOs in clearing_house.go * feat: add liquidateresp as a proto type * feat: Remove duplicate sdk.AccAddress transform * Update x/perp/keeper/liquidate_test.go Co-authored-by: Walter White <[email protected]> * fix: added check to please linter * Add distribute liquidate rewards * Small refactoring * Update state.proto Co-authored-by: Unique-Divine <[email protected]> Co-authored-by: AgentSmithMatrix <[email protected]> Co-authored-by: MD <[email protected]> Co-authored-by: Mat-Cosmos <[email protected]>
- Loading branch information
1 parent
2f34aa5
commit f04da51
Showing
7 changed files
with
758 additions
and
91 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,83 @@ | ||
package keeper | ||
|
||
import ( | ||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
|
||
"github.com/NibiruChain/nibiru/x/common" | ||
"github.com/NibiruChain/nibiru/x/perp/events" | ||
"github.com/NibiruChain/nibiru/x/perp/types" | ||
) | ||
|
||
func (k Keeper) distributeLiquidateRewards( | ||
ctx sdk.Context, liquidateResp types.LiquidateResp) (err error) { | ||
// -------------------------------------------------------------- | ||
// Preliminary validations | ||
// -------------------------------------------------------------- | ||
|
||
// validate response | ||
err = liquidateResp.Validate() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// validate pair | ||
pair, err := common.NewTokenPairFromStr(liquidateResp.PositionResp.Position.Pair) | ||
if err != nil { | ||
return err | ||
} | ||
err = k.requireVpool(ctx, pair) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// -------------------------------------------------------------- | ||
// Distribution of rewards | ||
// -------------------------------------------------------------- | ||
|
||
vaultAddr := k.AccountKeeper.GetModuleAddress(types.VaultModuleAccount) | ||
perpEFAddr := k.AccountKeeper.GetModuleAddress(types.PerpEFModuleAccount) | ||
|
||
// Transfer fee from vault to PerpEF | ||
feeToPerpEF := liquidateResp.FeeToPerpEcosystemFund.RoundInt() | ||
if feeToPerpEF.IsPositive() { | ||
coinToPerpEF := sdk.NewCoin( | ||
pair.GetQuoteTokenDenom(), feeToPerpEF) | ||
err = k.BankKeeper.SendCoinsFromModuleToModule( | ||
ctx, | ||
/* from */ types.VaultModuleAccount, | ||
/* to */ types.PerpEFModuleAccount, | ||
sdk.NewCoins(coinToPerpEF), | ||
) | ||
if err != nil { | ||
return err | ||
} | ||
events.EmitTransfer(ctx, | ||
/* coin */ coinToPerpEF, | ||
/* from */ vaultAddr.String(), | ||
/* to */ perpEFAddr.String(), | ||
) | ||
} | ||
|
||
// Transfer fee from PerpEF to liquidator | ||
feeToLiquidator := liquidateResp.FeeToLiquidator.RoundInt() | ||
if feeToLiquidator.IsPositive() { | ||
coinToLiquidator := sdk.NewCoin( | ||
pair.GetQuoteTokenDenom(), feeToLiquidator) | ||
err = k.BankKeeper.SendCoinsFromModuleToAccount( | ||
ctx, | ||
/* from */ types.PerpEFModuleAccount, | ||
/* to */ liquidateResp.Liquidator, | ||
sdk.NewCoins(coinToLiquidator), | ||
) | ||
if err != nil { | ||
return err | ||
} | ||
events.EmitTransfer(ctx, | ||
/* coin */ coinToLiquidator, | ||
/* from */ perpEFAddr.String(), | ||
/* to */ liquidateResp.Liquidator.String(), | ||
) | ||
} | ||
|
||
return nil | ||
} |
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,168 @@ | ||
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/common" | ||
"github.com/NibiruChain/nibiru/x/perp/events" | ||
"github.com/NibiruChain/nibiru/x/perp/types" | ||
|
||
"github.com/NibiruChain/nibiru/x/testutil/sample" | ||
) | ||
|
||
func Test_distributeLiquidateRewards_Error(t *testing.T) { | ||
testcases := []struct { | ||
name string | ||
test func() | ||
}{ | ||
{ | ||
name: "empty LiquidateResponse fails validation - error", | ||
test: func() { | ||
perpKeeper, _, ctx := getKeeper(t) | ||
err := perpKeeper.distributeLiquidateRewards(ctx, | ||
types.LiquidateResp{}) | ||
require.Error(t, err) | ||
require.ErrorContains(t, err, "must not have nil fields") | ||
}, | ||
}, | ||
{ | ||
name: "invalid liquidator - panic", | ||
test: func() { | ||
perpKeeper, _, ctx := getKeeper(t) | ||
|
||
require.Panics(t, func() { | ||
err := perpKeeper.distributeLiquidateRewards(ctx, | ||
types.LiquidateResp{BadDebt: sdk.OneDec(), FeeToLiquidator: sdk.OneDec(), | ||
FeeToPerpEcosystemFund: sdk.OneDec(), | ||
Liquidator: sdk.AccAddress{}, | ||
}, | ||
) | ||
require.Error(t, err) | ||
}) | ||
}, | ||
}, | ||
{ | ||
name: "invalid pair - error", | ||
test: func() { | ||
perpKeeper, _, ctx := getKeeper(t) | ||
liquidator := sample.AccAddress() | ||
err := perpKeeper.distributeLiquidateRewards(ctx, | ||
types.LiquidateResp{BadDebt: sdk.OneDec(), FeeToLiquidator: sdk.OneDec(), | ||
FeeToPerpEcosystemFund: sdk.OneDec(), | ||
Liquidator: liquidator, | ||
PositionResp: &types.PositionResp{ | ||
Position: &types.Position{ | ||
Pair: "dai:usdc:usdt", | ||
}}, | ||
}, | ||
) | ||
require.Error(t, err) | ||
require.ErrorContains(t, err, common.ErrInvalidTokenPair.Error()) | ||
}, | ||
}, | ||
{ | ||
name: "vpool does not exist - error", | ||
test: func() { | ||
perpKeeper, mocks, ctx := getKeeper(t) | ||
liquidator := sample.AccAddress() | ||
pair := common.TokenPair("xxx:yyy") | ||
mocks.mockVpoolKeeper.EXPECT().ExistsPool(ctx, pair).Return(false) | ||
err := perpKeeper.distributeLiquidateRewards(ctx, | ||
types.LiquidateResp{BadDebt: sdk.OneDec(), FeeToLiquidator: sdk.OneDec(), | ||
FeeToPerpEcosystemFund: sdk.OneDec(), | ||
Liquidator: liquidator, | ||
PositionResp: &types.PositionResp{ | ||
Position: &types.Position{ | ||
Pair: pair.String(), | ||
}}, | ||
}, | ||
) | ||
require.Error(t, err) | ||
require.ErrorContains(t, err, types.ErrPairNotFound.Error()) | ||
}, | ||
}, | ||
} | ||
|
||
for _, tc := range testcases { | ||
tc := tc | ||
t.Run(tc.name, func(t *testing.T) { | ||
tc.test() | ||
}) | ||
} | ||
} | ||
|
||
func Test_distributeLiquidateRewards_Happy(t *testing.T) { | ||
testcases := []struct { | ||
name string | ||
test func() | ||
}{ | ||
{ | ||
name: "healthy liquidation", | ||
test: func() { | ||
perpKeeper, mocks, ctx := getKeeper(t) | ||
liquidator := sample.AccAddress() | ||
pair := common.TokenPair("xxx:yyy") | ||
|
||
mocks.mockVpoolKeeper.EXPECT().ExistsPool(ctx, pair).Return(true) | ||
|
||
vaultAddr := authtypes.NewModuleAddress(types.VaultModuleAccount) | ||
perpEFAddr := authtypes.NewModuleAddress(types.VaultModuleAccount) | ||
mocks.mockAccountKeeper.EXPECT().GetModuleAddress( | ||
types.VaultModuleAccount). | ||
Return(vaultAddr) | ||
mocks.mockAccountKeeper.EXPECT().GetModuleAddress( | ||
types.PerpEFModuleAccount). | ||
Return(perpEFAddr) | ||
|
||
mocks.mockBankKeeper.EXPECT().SendCoinsFromModuleToModule( | ||
ctx, types.VaultModuleAccount, types.PerpEFModuleAccount, | ||
sdk.NewCoins(sdk.NewCoin("yyy", sdk.OneInt())), | ||
).Return(nil) | ||
mocks.mockBankKeeper.EXPECT().SendCoinsFromModuleToAccount( | ||
ctx, types.PerpEFModuleAccount, liquidator, | ||
sdk.NewCoins(sdk.NewCoin("yyy", sdk.OneInt())), | ||
).Return(nil) | ||
|
||
err := perpKeeper.distributeLiquidateRewards(ctx, | ||
types.LiquidateResp{BadDebt: sdk.OneDec(), FeeToLiquidator: sdk.OneDec(), | ||
FeeToPerpEcosystemFund: sdk.OneDec(), | ||
Liquidator: liquidator, | ||
PositionResp: &types.PositionResp{ | ||
Position: &types.Position{ | ||
Pair: pair.String(), | ||
}}, | ||
}, | ||
) | ||
require.NoError(t, err) | ||
|
||
expectedEvents := []sdk.Event{ | ||
events.NewTransferEvent( | ||
/* coin */ sdk.NewCoin("yyy", sdk.OneInt()), | ||
/* from */ vaultAddr.String(), | ||
/* to */ perpEFAddr.String(), | ||
), | ||
events.NewTransferEvent( | ||
/* coin */ sdk.NewCoin("yyy", sdk.OneInt()), | ||
/* from */ perpEFAddr.String(), | ||
/* to */ liquidator.String(), | ||
), | ||
} | ||
for _, event := range expectedEvents { | ||
assert.Contains(t, ctx.EventManager().Events(), event) | ||
} | ||
}, | ||
}, | ||
} | ||
|
||
for _, tc := range testcases { | ||
tc := tc | ||
t.Run(tc.name, func(t *testing.T) { | ||
tc.test() | ||
}) | ||
} | ||
} |
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
Oops, something went wrong.