Skip to content

Commit

Permalink
Merge pull request #138 from onomyprotocol/dong/BurnShortfall
Browse files Browse the repository at this point in the history
vaults(gov): add gov handle shortfall from reserve
  • Loading branch information
DongLieu authored Jan 22, 2025
2 parents 448114a + 0f6b62d commit fab45ab
Show file tree
Hide file tree
Showing 10 changed files with 1,076 additions and 81 deletions.
10 changes: 10 additions & 0 deletions proto/reserve/vaults/proposal.proto
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,13 @@ message UpdatesCollateralProposal {
string description = 2;
MsgUpdatesCollateral updates_collateral = 3 [(gogoproto.nullable) = false];
}

message BurnShortfallProposal {
option (gogoproto.goproto_getters) = false;
option (cosmos_proto.implements_interface) = "cosmos.gov.v1beta1.Content";
option (amino.name) = "reserve/BurnShortfallProposal";

string title = 1;
string description = 2;
MsgBurnShortfall burn_shortfall = 3 [(gogoproto.nullable) = false];
}
19 changes: 19 additions & 0 deletions proto/reserve/vaults/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ service Msg {

// Close defines a method for close vault
rpc Close(MsgClose) returns (MsgCloseResponse);

rpc BurnShortfall(MsgBurnShortfall) returns (MsgBurnShortfallResponse);
}

message MsgUpdateParams {
Expand Down Expand Up @@ -295,3 +297,20 @@ message MsgClose {

// MsgRepayResponse defines the Msg/Mint response type.
message MsgCloseResponse {}

message MsgBurnShortfall {
option (gogoproto.equal) = false;
option (gogoproto.goproto_getters) = false;
option (cosmos.msg.v1.signer) = "authority";

string authority = 1 [ (cosmos_proto.scalar) = "cosmos.AddressString" ];
string mint_denom = 2;
string amount = 3 [
(cosmos_proto.scalar) = "cosmos.Int",
(gogoproto.customtype) = "cosmossdk.io/math.Int",
(amino.dont_omitempty) = true,
(gogoproto.nullable) = false
];
}

message MsgBurnShortfallResponse {}
30 changes: 29 additions & 1 deletion x/vaults/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -263,4 +263,32 @@ func (k *Keeper) burnDebt(ctx context.Context, vmKey string, vm types.VaultManag
}
vm.MintAvailable = vm.MintAvailable.Add(coin.Amount)
return k.VaultsManager.Set(ctx, vmKey, vm)
}
}

func (k *Keeper) BurnShortfallByMintDenom(ctx context.Context, mintDenom string, amount math.Int) error {
// get amount shortfall by mintdenom
amountShortfall, err := k.ShortfallAmount.Get(ctx, mintDenom)
if err != nil {
return err
}
if amountShortfall.LT(amount) {
return fmt.Errorf("amount shortfall is less than")
}
// get amount reserve by mintdenom
amountReserve := k.BankKeeper.GetAllBalances(ctx, k.accountKeeper.GetModuleAddress(types.ReserveModuleName)).AmountOf(mintDenom)
if amountReserve.LT(amount) {
return fmt.Errorf("amount reserve is less than")
}

// Burn token from Reserve module
if err := k.BankKeeper.BurnCoins(ctx, types.ReserveModuleName, sdk.NewCoins(sdk.NewCoin(mintDenom, amount))); err != nil {
return err
}

// Update shortfall amount after burning
remainingShortfall := amountShortfall.Sub(amount)
if err := k.ShortfallAmount.Set(ctx, mintDenom, remainingShortfall); err != nil {
return err
}
return nil
}
24 changes: 24 additions & 0 deletions x/vaults/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package keeper
import (
"context"
"fmt"
"slices"

errorsmod "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"
Expand Down Expand Up @@ -180,3 +181,26 @@ func (k msgServer) Close(ctx context.Context, msg *types.MsgClose) (*types.MsgCl
}
return &types.MsgCloseResponse{}, nil
}

// use mintDenom in reserve to burn Shortfall via gov
func (k msgServer) BurnShortfall(ctx context.Context, msg *types.MsgBurnShortfall) (*types.MsgBurnShortfallResponse, error) {
err := msg.ValidateBasic()
if err != nil {
return &types.MsgBurnShortfallResponse{}, err
}

if k.authority != msg.Authority {
return nil, errorsmod.Wrapf(types.ErrInvalidSigner, "invalid authority; expected %s, got %s", k.authority, msg.Authority)
}

if !slices.Contains(k.GetParams(ctx).AllowedMintDenom, msg.MintDenom) {
return nil, fmt.Errorf("denom %s is not in the allowed mint denom list", msg.MintDenom)
}

err = k.BurnShortfallByMintDenom(ctx, msg.MintDenom, msg.Amount)
if err != nil {
return nil, err
}

return &types.MsgBurnShortfallResponse{}, nil
}
194 changes: 194 additions & 0 deletions x/vaults/keeper/msg_server_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
package keeper_test

import (
sdk "github.com/cosmos/cosmos-sdk/types"

"cosmossdk.io/math"

"github.com/onomyprotocol/reserve/x/vaults/types"
)

func (s *KeeperTestSuite) TestBurnShortfallByMintDenom() {
testcases := []struct {
name string
mintDenom string
setup func() types.MsgBurnShortfall
expShortfallAmountAfterBurn math.Int
expReserveBalcesAfterBurn math.Int
expPass bool
}{
{
name: "success burn part",
mintDenom: "fxUSD",
setup: func() types.MsgBurnShortfall {
// make sure reserve has money
mintCoin := sdk.NewCoins(sdk.NewCoin("fxUSD", math.NewInt(10_000_000)))
s.FundAccount(s.TestAccs[0], types.ModuleName, mintCoin)
err := s.k.BankKeeper.SendCoinsFromAccountToModule(s.Ctx, s.TestAccs[0], types.ReserveModuleName, mintCoin)
s.Require().NoError(err)

// make sure Guaranteed Shortfall Amount
err = s.k.ShortfallAmount.Set(s.Ctx, "fxUSD", math.NewInt(5_000_000))
s.Require().NoError(err)
return types.MsgBurnShortfall{
Authority: "onomy10d07y265gmmuvt4z0w9aw880jnsr700jqr8n8k",
MintDenom: "fxUSD",
Amount: math.NewInt(1_000_000),
}
},
expShortfallAmountAfterBurn: math.NewInt(4_000_000),
expReserveBalcesAfterBurn: math.NewInt(9_000_000),
expPass: true,
},
{
name: "success maximum burn, shortfallAmount is less than reserve balances",
mintDenom: "fxUSD",
setup: func() types.MsgBurnShortfall {
// make sure reserve has money
mintCoin := sdk.NewCoins(sdk.NewCoin("fxUSD", math.NewInt(10_000_000)))
s.FundAccount(s.TestAccs[0], types.ModuleName, mintCoin)
err := s.k.BankKeeper.SendCoinsFromAccountToModule(s.Ctx, s.TestAccs[0], types.ReserveModuleName, mintCoin)
s.Require().NoError(err)

// make sure Guaranteed Shortfall Amount
err = s.k.ShortfallAmount.Set(s.Ctx, "fxUSD", math.NewInt(1_000_000))
s.Require().NoError(err)
return types.MsgBurnShortfall{
Authority: "onomy10d07y265gmmuvt4z0w9aw880jnsr700jqr8n8k",
MintDenom: "fxUSD",
Amount: math.NewInt(1_000_000),
}
},
expShortfallAmountAfterBurn: math.ZeroInt(),
expReserveBalcesAfterBurn: math.NewInt(9_000_000),
expPass: true,
},
{
name: "success maximum burn, reserve balancess is less than shortfallAmount",
mintDenom: "fxUSD",
setup: func() types.MsgBurnShortfall {
// make sure reserve has money
mintCoin := sdk.NewCoins(sdk.NewCoin("fxUSD", math.NewInt(1_000_000)))
s.FundAccount(s.TestAccs[0], types.ModuleName, mintCoin)
err := s.k.BankKeeper.SendCoinsFromAccountToModule(s.Ctx, s.TestAccs[0], types.ReserveModuleName, mintCoin)
s.Require().NoError(err)

// make sure Guaranteed Shortfall Amount
err = s.k.ShortfallAmount.Set(s.Ctx, "fxUSD", math.NewInt(10_000_000))
s.Require().NoError(err)
return types.MsgBurnShortfall{
Authority: "onomy10d07y265gmmuvt4z0w9aw880jnsr700jqr8n8k",
MintDenom: "fxUSD",
Amount: math.NewInt(1_000_000),
}
},
expShortfallAmountAfterBurn: math.NewInt(9_000_000),
expReserveBalcesAfterBurn: math.ZeroInt(),
expPass: true,
},
{
name: "fail, reserve balancess no money",
mintDenom: "fxUSD",
setup: func() types.MsgBurnShortfall {
// make sure Guaranteed Shortfall Amount
err := s.k.ShortfallAmount.Set(s.Ctx, "fxUSD", math.NewInt(10_000_000))
s.Require().NoError(err)
return types.MsgBurnShortfall{
Authority: "onomy10d07y265gmmuvt4z0w9aw880jnsr700jqr8n8k",
MintDenom: "fxUSD",
Amount: math.NewInt(1_000_000),
}
},
expPass: false,
},
{
name: "fail, government account not the signatory for the proposed message",
mintDenom: "fxUSD",
setup: func() types.MsgBurnShortfall {
// make sure reserve has money
mintCoin := sdk.NewCoins(sdk.NewCoin("fxUSD", math.NewInt(1_000_000)))
s.FundAccount(s.TestAccs[0], types.ModuleName, mintCoin)
err := s.k.BankKeeper.SendCoinsFromAccountToModule(s.Ctx, s.TestAccs[0], types.ReserveModuleName, mintCoin)
s.Require().NoError(err)

// make sure Guaranteed Shortfall Amount
err = s.k.ShortfallAmount.Set(s.Ctx, "fxUSD", math.NewInt(10_000_000))
s.Require().NoError(err)
return types.MsgBurnShortfall{
Authority: s.TestAccs[0].String(),
MintDenom: "fxUSD",
Amount: math.NewInt(1_000_000),
}
},
expPass: false,
},
{
name: "fail, denom is not in the allowed mint denom list",
mintDenom: "fxUSD",
setup: func() types.MsgBurnShortfall {
// make sure reserve has money
mintCoin := sdk.NewCoins(sdk.NewCoin("fxUSD", math.NewInt(1_000_000)))
s.FundAccount(s.TestAccs[0], types.ModuleName, mintCoin)
err := s.k.BankKeeper.SendCoinsFromAccountToModule(s.Ctx, s.TestAccs[0], types.ReserveModuleName, mintCoin)
s.Require().NoError(err)

// make sure Guaranteed Shortfall Amount
err = s.k.ShortfallAmount.Set(s.Ctx, "fxUSD", math.NewInt(10_000_000))
s.Require().NoError(err)
return types.MsgBurnShortfall{
Authority: s.TestAccs[0].String(),
MintDenom: "atom",
Amount: math.NewInt(1_000_000),
}
},
expPass: false,
},
{
name: "fail, burn all",
mintDenom: "fxUSD",
setup: func() types.MsgBurnShortfall {
// make sure reserve has money
mintCoin := sdk.NewCoins(sdk.NewCoin("fxUSD", math.NewInt(5_000_000)))
s.FundAccount(s.TestAccs[0], types.ModuleName, mintCoin)
err := s.k.BankKeeper.SendCoinsFromAccountToModule(s.Ctx, s.TestAccs[0], types.ReserveModuleName, mintCoin)
s.Require().NoError(err)

// make sure Guaranteed Shortfall Amount
err = s.k.ShortfallAmount.Set(s.Ctx, "fxUSD", math.NewInt(5_000_000))
s.Require().NoError(err)
return types.MsgBurnShortfall{
Authority: "onomy10d07y265gmmuvt4z0w9aw880jnsr700jqr8n8k",
MintDenom: "fxUSD",
Amount: math.NewInt(10_000_000),
}
},
expPass: false,
},
}

for _, t := range testcases {
s.Run(t.name, func() {
s.SetupTest()
msg := t.setup()

// burn Shortfall
_, err := s.msgServer.BurnShortfall(s.Ctx, &msg)
if t.expPass {
s.Require().NoError(err)

// check reserve balances after burn
reserveBalces := s.k.BankKeeper.GetAllBalances(s.Ctx, s.App.AccountKeeper.GetModuleAddress(types.ReserveModuleName))
s.Require().True(reserveBalces.AmountOf(t.mintDenom).Equal(t.expReserveBalcesAfterBurn))

// check ShortfallAmount after burn

shortfallAmountAfterBurn, err := s.k.ShortfallAmount.Get(s.Ctx, t.mintDenom)
s.Require().NoError(err)

s.Require().True(shortfallAmountAfterBurn.Equal(t.expShortfallAmountAfterBurn))
} else {
s.Require().Error(err)
}
})
}
}
3 changes: 3 additions & 0 deletions x/vaults/module/proposal_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ func NewVaultsProposalHandler(k *keeper.Keeper) govtypes.Handler {
case *types.UpdatesCollateralProposal:
_, err := msgSv.UpdatesCollateral(ctx, types.NewMsgUpdatesCollateral(c))
return err
case *types.BurnShortfallProposal:
_, err := msgSv.BurnShortfall(ctx, types.NewMsgBurnShortfall(c))
return err
default:
return errorsmod.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized %s proposal content type: %T", types.ModuleName, c)
}
Expand Down
3 changes: 3 additions & 0 deletions x/vaults/types/codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) {
cdc.RegisterConcrete(&ActiveCollateralProposal{}, "reserve/ActiveCollateralProposal", nil)
cdc.RegisterConcrete(&UpdatesCollateralProposal{}, "reserve/UpdatesCollateralProposal", nil)
cdc.RegisterConcrete(&BurnShortfallProposal{}, "reserve/BurnShortfallProposal", nil)
}

func RegisterInterfaces(registry cdctypes.InterfaceRegistry) {
Expand All @@ -20,6 +21,7 @@ func RegisterInterfaces(registry cdctypes.InterfaceRegistry) {
registry.RegisterImplementations((*sdk.Msg)(nil),
&MsgUpdateParams{},
&MsgActiveCollateral{},
&MsgBurnShortfall{},
&MsgUpdatesCollateral{},
&MsgCreateVault{},
&MsgDeposit{},
Expand All @@ -33,6 +35,7 @@ func RegisterInterfaces(registry cdctypes.InterfaceRegistry) {
(*govtypes.Content)(nil),
&ActiveCollateralProposal{},
&UpdatesCollateralProposal{},
&BurnShortfallProposal{},
)

}
Loading

0 comments on commit fab45ab

Please sign in to comment.