mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-12-24 15:25:18 +00:00
Hard Audit: enable users to repay another account's borrows (#801)
* add owner to repay msg * pass owner and sender to repay function * make owner arg an optional flag * make owner optional for REST
This commit is contained in:
parent
1b2cfa6d1a
commit
5af50e1a2d
@ -6,6 +6,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
"github.com/cosmos/cosmos-sdk/client"
|
||||||
"github.com/cosmos/cosmos-sdk/client/context"
|
"github.com/cosmos/cosmos-sdk/client/context"
|
||||||
@ -33,13 +34,20 @@ func GetTxCmd(cdc *codec.Codec) *cobra.Command {
|
|||||||
getCmdDeposit(cdc),
|
getCmdDeposit(cdc),
|
||||||
getCmdWithdraw(cdc),
|
getCmdWithdraw(cdc),
|
||||||
getCmdBorrow(cdc),
|
getCmdBorrow(cdc),
|
||||||
|
addOptionalFlag(getCmdRepay(cdc), flagOwner, "", "original borrower's address whose loan will be repaid"),
|
||||||
getCmdLiquidate(cdc),
|
getCmdLiquidate(cdc),
|
||||||
getCmdRepay(cdc),
|
|
||||||
)...)
|
)...)
|
||||||
|
|
||||||
return hardTxCmd
|
return hardTxCmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// addFlag adds a cobra flag and binds it using viper
|
||||||
|
func addOptionalFlag(cmd *cobra.Command, flagName, flagValue, flagUsage string) *cobra.Command {
|
||||||
|
cmd.Flags().String(flagName, flagValue, flagUsage)
|
||||||
|
viper.BindPFlag(flagName, cmd.Flags().Lookup(flagName))
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
func getCmdDeposit(cdc *codec.Codec) *cobra.Command {
|
func getCmdDeposit(cdc *codec.Codec) *cobra.Command {
|
||||||
return &cobra.Command{
|
return &cobra.Command{
|
||||||
Use: "deposit [amount]",
|
Use: "deposit [amount]",
|
||||||
@ -91,7 +99,7 @@ func getCmdWithdraw(cdc *codec.Codec) *cobra.Command {
|
|||||||
|
|
||||||
func getCmdBorrow(cdc *codec.Codec) *cobra.Command {
|
func getCmdBorrow(cdc *codec.Codec) *cobra.Command {
|
||||||
return &cobra.Command{
|
return &cobra.Command{
|
||||||
Use: "borrow [1000000000ukava]",
|
Use: "borrow [amount]",
|
||||||
Short: "borrow tokens from the hard protocol",
|
Short: "borrow tokens from the hard protocol",
|
||||||
Long: strings.TrimSpace(`borrows tokens from the hard protocol`),
|
Long: strings.TrimSpace(`borrows tokens from the hard protocol`),
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
@ -119,24 +127,40 @@ func getCmdBorrow(cdc *codec.Codec) *cobra.Command {
|
|||||||
|
|
||||||
func getCmdRepay(cdc *codec.Codec) *cobra.Command {
|
func getCmdRepay(cdc *codec.Codec) *cobra.Command {
|
||||||
return &cobra.Command{
|
return &cobra.Command{
|
||||||
Use: "repay [1000000000ukava]",
|
Use: "repay [amount]",
|
||||||
Short: "repay tokens to the hard protocol",
|
Short: "repay tokens to the hard protocol",
|
||||||
Long: strings.TrimSpace(`repay tokens to the hard protocol`),
|
Long: strings.TrimSpace(`repay tokens to the hard protocol with optional --owner param to repay another account's loan`),
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
Example: fmt.Sprintf(
|
Example: strings.TrimSpace(`
|
||||||
`%s tx %s repay 1000000000ukava,25000000000bnb --from <key>`, version.ClientName, types.ModuleName,
|
kvcli tx hard repay 1000000000ukava --from <key>
|
||||||
),
|
kvcli tx hard repay 1000000000ukava,25000000000bnb --from <key>
|
||||||
|
kvcli tx hard repay 1000000000ukava,25000000000bnb --owner <owner-address> --from <key>
|
||||||
|
`),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
inBuf := bufio.NewReader(cmd.InOrStdin())
|
inBuf := bufio.NewReader(cmd.InOrStdin())
|
||||||
cliCtx := context.NewCLIContext().WithCodec(cdc)
|
cliCtx := context.NewCLIContext().WithCodec(cdc)
|
||||||
txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(utils.GetTxEncoder(cdc))
|
txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(utils.GetTxEncoder(cdc))
|
||||||
|
|
||||||
|
var owner sdk.AccAddress
|
||||||
|
ownerStr := viper.GetString(flagOwner)
|
||||||
|
|
||||||
|
// Parse optional owner argument or default to sender
|
||||||
|
if len(ownerStr) > 0 {
|
||||||
|
ownerAddr, err := sdk.AccAddressFromBech32(ownerStr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
owner = ownerAddr
|
||||||
|
} else {
|
||||||
|
owner = cliCtx.GetFromAddress()
|
||||||
|
}
|
||||||
|
|
||||||
coins, err := sdk.ParseCoins(args[0])
|
coins, err := sdk.ParseCoins(args[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
msg := types.NewMsgRepay(cliCtx.GetFromAddress(), coins)
|
msg := types.NewMsgRepay(cliCtx.GetFromAddress(), owner, coins)
|
||||||
if err := msg.ValidateBasic(); err != nil {
|
if err := msg.ValidateBasic(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -47,6 +47,7 @@ type PostBorrowReq struct {
|
|||||||
type PostRepayReq struct {
|
type PostRepayReq struct {
|
||||||
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
|
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
|
||||||
From sdk.AccAddress `json:"from" yaml:"from"`
|
From sdk.AccAddress `json:"from" yaml:"from"`
|
||||||
|
Owner sdk.AccAddress `json:"owner" yaml:"owner"`
|
||||||
Amount sdk.Coins `json:"amount" yaml:"amount"`
|
Amount sdk.Coins `json:"amount" yaml:"amount"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,7 +97,12 @@ func postRepayHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
msg := types.NewMsgRepay(req.From, req.Amount)
|
owner := req.Owner
|
||||||
|
if owner.Empty() {
|
||||||
|
owner = req.From
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := types.NewMsgRepay(req.From, owner, req.Amount)
|
||||||
if err := msg.ValidateBasic(); err != nil {
|
if err := msg.ValidateBasic(); err != nil {
|
||||||
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||||
return
|
return
|
||||||
|
@ -84,7 +84,7 @@ func handleMsgBorrow(ctx sdk.Context, k keeper.Keeper, msg types.MsgBorrow) (*sd
|
|||||||
}
|
}
|
||||||
|
|
||||||
func handleMsgRepay(ctx sdk.Context, k keeper.Keeper, msg types.MsgRepay) (*sdk.Result, error) {
|
func handleMsgRepay(ctx sdk.Context, k keeper.Keeper, msg types.MsgRepay) (*sdk.Result, error) {
|
||||||
err := k.Repay(ctx, msg.Sender, msg.Amount)
|
err := k.Repay(ctx, msg.Sender, msg.Owner, msg.Amount)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -8,9 +8,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Repay borrowed funds
|
// Repay borrowed funds
|
||||||
func (k Keeper) Repay(ctx sdk.Context, sender sdk.AccAddress, coins sdk.Coins) error {
|
func (k Keeper) Repay(ctx sdk.Context, sender, owner sdk.AccAddress, coins sdk.Coins) error {
|
||||||
// Check borrow exists here to avoid duplicating store read in ValidateRepay
|
// Check borrow exists here to avoid duplicating store read in ValidateRepay
|
||||||
borrow, found := k.GetBorrow(ctx, sender)
|
borrow, found := k.GetBorrow(ctx, owner)
|
||||||
if !found {
|
if !found {
|
||||||
return types.ErrBorrowNotFound
|
return types.ErrBorrowNotFound
|
||||||
}
|
}
|
||||||
@ -18,10 +18,10 @@ func (k Keeper) Repay(ctx sdk.Context, sender sdk.AccAddress, coins sdk.Coins) e
|
|||||||
k.BeforeBorrowModified(ctx, borrow)
|
k.BeforeBorrowModified(ctx, borrow)
|
||||||
|
|
||||||
// Sync borrow interest so loan is up-to-date
|
// Sync borrow interest so loan is up-to-date
|
||||||
k.SyncBorrowInterest(ctx, sender)
|
k.SyncBorrowInterest(ctx, owner)
|
||||||
|
|
||||||
// Validate requested repay
|
// Validate that sender holds coins for repayment
|
||||||
err := k.ValidateRepay(ctx, sender, coins)
|
err = k.ValidateRepay(ctx, sender, coins)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -69,6 +69,7 @@ func (k Keeper) Repay(ctx sdk.Context, sender sdk.AccAddress, coins sdk.Coins) e
|
|||||||
sdk.NewEvent(
|
sdk.NewEvent(
|
||||||
types.EventTypeHardRepay,
|
types.EventTypeHardRepay,
|
||||||
sdk.NewAttribute(types.AttributeKeySender, sender.String()),
|
sdk.NewAttribute(types.AttributeKeySender, sender.String()),
|
||||||
|
sdk.NewAttribute(types.AttributeKeyOwner, owner.String()),
|
||||||
sdk.NewAttribute(types.AttributeKeyRepayCoins, payment.String()),
|
sdk.NewAttribute(types.AttributeKeyRepayCoins, payment.String()),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -211,7 +211,7 @@ func (suite *KeeperTestSuite) TestRepay() {
|
|||||||
err = suite.keeper.Borrow(suite.ctx, tc.args.borrower, tc.args.borrowCoins)
|
err = suite.keeper.Borrow(suite.ctx, tc.args.borrower, tc.args.borrowCoins)
|
||||||
suite.Require().NoError(err)
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
err = suite.keeper.Repay(suite.ctx, tc.args.borrower, tc.args.repayCoins)
|
err = suite.keeper.Repay(suite.ctx, tc.args.borrower, tc.args.borrower, tc.args.repayCoins)
|
||||||
if tc.errArgs.expectPass {
|
if tc.errArgs.expectPass {
|
||||||
suite.Require().NoError(err)
|
suite.Require().NoError(err)
|
||||||
// If we overpaid expect an adjustment
|
// If we overpaid expect an adjustment
|
||||||
|
@ -356,7 +356,7 @@ func (suite *KeeperTestSuite) TestLtvWithdraw() {
|
|||||||
suite.Require().True(strings.Contains(err.Error(), tc.errArgs.contains))
|
suite.Require().True(strings.Contains(err.Error(), tc.errArgs.contains))
|
||||||
|
|
||||||
// Repay the initial principal
|
// Repay the initial principal
|
||||||
err = suite.keeper.Repay(suite.ctx, tc.args.borrower, tc.args.borrowCoins)
|
err = suite.keeper.Repay(suite.ctx, tc.args.borrower, tc.args.borrower, tc.args.borrowCoins)
|
||||||
suite.Require().NoError(err)
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
// Attempted withdraw of all deposited coins fails as user hasn't repaid interest debt
|
// Attempted withdraw of all deposited coins fails as user hasn't repaid interest debt
|
||||||
|
@ -26,4 +26,5 @@ const (
|
|||||||
AttributeKeyLiquidatedCoins = "liquidated_coins"
|
AttributeKeyLiquidatedCoins = "liquidated_coins"
|
||||||
AttributeKeyKeeper = "keeper"
|
AttributeKeyKeeper = "keeper"
|
||||||
AttributeKeyKeeperRewardCoins = "keeper_reward_coins"
|
AttributeKeyKeeperRewardCoins = "keeper_reward_coins"
|
||||||
|
AttributeKeyOwner = "owner"
|
||||||
)
|
)
|
||||||
|
@ -169,13 +169,15 @@ func (msg MsgBorrow) String() string {
|
|||||||
// MsgRepay repays funds to the hard module.
|
// MsgRepay repays funds to the hard module.
|
||||||
type MsgRepay struct {
|
type MsgRepay struct {
|
||||||
Sender sdk.AccAddress `json:"sender" yaml:"sender"`
|
Sender sdk.AccAddress `json:"sender" yaml:"sender"`
|
||||||
|
Owner sdk.AccAddress `json:"owner" yaml:"owner"`
|
||||||
Amount sdk.Coins `json:"amount" yaml:"amount"`
|
Amount sdk.Coins `json:"amount" yaml:"amount"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMsgRepay returns a new MsgRepay
|
// NewMsgRepay returns a new MsgRepay
|
||||||
func NewMsgRepay(sender sdk.AccAddress, amount sdk.Coins) MsgRepay {
|
func NewMsgRepay(sender, owner sdk.AccAddress, amount sdk.Coins) MsgRepay {
|
||||||
return MsgRepay{
|
return MsgRepay{
|
||||||
Sender: sender,
|
Sender: sender,
|
||||||
|
Owner: owner,
|
||||||
Amount: amount,
|
Amount: amount,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -191,6 +193,9 @@ func (msg MsgRepay) ValidateBasic() error {
|
|||||||
if msg.Sender.Empty() {
|
if msg.Sender.Empty() {
|
||||||
return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "sender address cannot be empty")
|
return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "sender address cannot be empty")
|
||||||
}
|
}
|
||||||
|
if msg.Owner.Empty() {
|
||||||
|
return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "owner address cannot be empty")
|
||||||
|
}
|
||||||
if !msg.Amount.IsValid() || msg.Amount.IsZero() {
|
if !msg.Amount.IsValid() || msg.Amount.IsZero() {
|
||||||
return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "repay amount %s", msg.Amount)
|
return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "repay amount %s", msg.Amount)
|
||||||
}
|
}
|
||||||
@ -212,8 +217,9 @@ func (msg MsgRepay) GetSigners() []sdk.AccAddress {
|
|||||||
func (msg MsgRepay) String() string {
|
func (msg MsgRepay) String() string {
|
||||||
return fmt.Sprintf(`Repay Message:
|
return fmt.Sprintf(`Repay Message:
|
||||||
Sender: %s
|
Sender: %s
|
||||||
|
Owner: %s
|
||||||
Amount: %s
|
Amount: %s
|
||||||
`, msg.Sender, msg.Amount)
|
`, msg.Sender, msg.Owner, msg.Amount)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MsgLiquidate attempts to liquidate a borrower's borrow
|
// MsgLiquidate attempts to liquidate a borrower's borrow
|
||||||
|
@ -152,6 +152,7 @@ func (suite *MsgTestSuite) TestMsgBorrow() {
|
|||||||
func (suite *MsgTestSuite) TestMsgRepay() {
|
func (suite *MsgTestSuite) TestMsgRepay() {
|
||||||
type args struct {
|
type args struct {
|
||||||
sender sdk.AccAddress
|
sender sdk.AccAddress
|
||||||
|
owner sdk.AccAddress
|
||||||
amount sdk.Coins
|
amount sdk.Coins
|
||||||
}
|
}
|
||||||
addrs := []sdk.AccAddress{
|
addrs := []sdk.AccAddress{
|
||||||
@ -167,6 +168,7 @@ func (suite *MsgTestSuite) TestMsgRepay() {
|
|||||||
name: "valid",
|
name: "valid",
|
||||||
args: args{
|
args: args{
|
||||||
sender: addrs[0],
|
sender: addrs[0],
|
||||||
|
owner: addrs[0],
|
||||||
amount: sdk.NewCoins(sdk.NewCoin("test", sdk.NewInt(1000000))),
|
amount: sdk.NewCoins(sdk.NewCoin("test", sdk.NewInt(1000000))),
|
||||||
},
|
},
|
||||||
expectPass: true,
|
expectPass: true,
|
||||||
@ -175,7 +177,7 @@ func (suite *MsgTestSuite) TestMsgRepay() {
|
|||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
suite.Run(tc.name, func() {
|
suite.Run(tc.name, func() {
|
||||||
msg := types.NewMsgRepay(tc.args.sender, tc.args.amount)
|
msg := types.NewMsgRepay(tc.args.sender, tc.args.owner, tc.args.amount)
|
||||||
err := msg.ValidateBasic()
|
err := msg.ValidateBasic()
|
||||||
if tc.expectPass {
|
if tc.expectPass {
|
||||||
suite.NoError(err)
|
suite.NoError(err)
|
||||||
|
Loading…
Reference in New Issue
Block a user