mirror of
				https://github.com/0glabs/0g-chain.git
				synced 2025-11-04 04:37:26 +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"
 | 
			
		||||
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
	"github.com/spf13/viper"
 | 
			
		||||
 | 
			
		||||
	"github.com/cosmos/cosmos-sdk/client"
 | 
			
		||||
	"github.com/cosmos/cosmos-sdk/client/context"
 | 
			
		||||
@ -33,13 +34,20 @@ func GetTxCmd(cdc *codec.Codec) *cobra.Command {
 | 
			
		||||
		getCmdDeposit(cdc),
 | 
			
		||||
		getCmdWithdraw(cdc),
 | 
			
		||||
		getCmdBorrow(cdc),
 | 
			
		||||
		addOptionalFlag(getCmdRepay(cdc), flagOwner, "", "original borrower's address whose loan will be repaid"),
 | 
			
		||||
		getCmdLiquidate(cdc),
 | 
			
		||||
		getCmdRepay(cdc),
 | 
			
		||||
	)...)
 | 
			
		||||
 | 
			
		||||
	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 {
 | 
			
		||||
	return &cobra.Command{
 | 
			
		||||
		Use:   "deposit [amount]",
 | 
			
		||||
@ -91,7 +99,7 @@ func getCmdWithdraw(cdc *codec.Codec) *cobra.Command {
 | 
			
		||||
 | 
			
		||||
func getCmdBorrow(cdc *codec.Codec) *cobra.Command {
 | 
			
		||||
	return &cobra.Command{
 | 
			
		||||
		Use:   "borrow [1000000000ukava]",
 | 
			
		||||
		Use:   "borrow [amount]",
 | 
			
		||||
		Short: "borrow tokens from the hard protocol",
 | 
			
		||||
		Long:  strings.TrimSpace(`borrows tokens from the hard protocol`),
 | 
			
		||||
		Args:  cobra.ExactArgs(1),
 | 
			
		||||
@ -119,24 +127,40 @@ func getCmdBorrow(cdc *codec.Codec) *cobra.Command {
 | 
			
		||||
 | 
			
		||||
func getCmdRepay(cdc *codec.Codec) *cobra.Command {
 | 
			
		||||
	return &cobra.Command{
 | 
			
		||||
		Use:   "repay [1000000000ukava]",
 | 
			
		||||
		Use:   "repay [amount]",
 | 
			
		||||
		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),
 | 
			
		||||
		Example: fmt.Sprintf(
 | 
			
		||||
			`%s tx %s repay 1000000000ukava,25000000000bnb --from <key>`, version.ClientName, types.ModuleName,
 | 
			
		||||
		),
 | 
			
		||||
		Example: strings.TrimSpace(`
 | 
			
		||||
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 {
 | 
			
		||||
			inBuf := bufio.NewReader(cmd.InOrStdin())
 | 
			
		||||
			cliCtx := context.NewCLIContext().WithCodec(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])
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			msg := types.NewMsgRepay(cliCtx.GetFromAddress(), coins)
 | 
			
		||||
			msg := types.NewMsgRepay(cliCtx.GetFromAddress(), owner, coins)
 | 
			
		||||
			if err := msg.ValidateBasic(); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
@ -47,6 +47,7 @@ type PostBorrowReq struct {
 | 
			
		||||
type PostRepayReq struct {
 | 
			
		||||
	BaseReq rest.BaseReq   `json:"base_req" yaml:"base_req"`
 | 
			
		||||
	From    sdk.AccAddress `json:"from" yaml:"from"`
 | 
			
		||||
	Owner   sdk.AccAddress `json:"owner" yaml:"owner"`
 | 
			
		||||
	Amount  sdk.Coins      `json:"amount" yaml:"amount"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -97,7 +97,12 @@ func postRepayHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
 | 
			
		||||
			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 {
 | 
			
		||||
			rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
 | 
			
		||||
			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) {
 | 
			
		||||
	err := k.Repay(ctx, msg.Sender, msg.Amount)
 | 
			
		||||
	err := k.Repay(ctx, msg.Sender, msg.Owner, msg.Amount)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@ -8,9 +8,9 @@ import (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 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
 | 
			
		||||
	borrow, found := k.GetBorrow(ctx, sender)
 | 
			
		||||
	borrow, found := k.GetBorrow(ctx, owner)
 | 
			
		||||
	if !found {
 | 
			
		||||
		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)
 | 
			
		||||
 | 
			
		||||
	// Sync borrow interest so loan is up-to-date
 | 
			
		||||
	k.SyncBorrowInterest(ctx, sender)
 | 
			
		||||
	k.SyncBorrowInterest(ctx, owner)
 | 
			
		||||
 | 
			
		||||
	// Validate requested repay
 | 
			
		||||
	err := k.ValidateRepay(ctx, sender, coins)
 | 
			
		||||
	// Validate that sender holds coins for repayment
 | 
			
		||||
	err = k.ValidateRepay(ctx, sender, coins)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
@ -69,6 +69,7 @@ func (k Keeper) Repay(ctx sdk.Context, sender sdk.AccAddress, coins sdk.Coins) e
 | 
			
		||||
		sdk.NewEvent(
 | 
			
		||||
			types.EventTypeHardRepay,
 | 
			
		||||
			sdk.NewAttribute(types.AttributeKeySender, sender.String()),
 | 
			
		||||
			sdk.NewAttribute(types.AttributeKeyOwner, owner.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)
 | 
			
		||||
			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 {
 | 
			
		||||
				suite.Require().NoError(err)
 | 
			
		||||
				// If we overpaid expect an adjustment
 | 
			
		||||
 | 
			
		||||
@ -356,7 +356,7 @@ func (suite *KeeperTestSuite) TestLtvWithdraw() {
 | 
			
		||||
			suite.Require().True(strings.Contains(err.Error(), tc.errArgs.contains))
 | 
			
		||||
 | 
			
		||||
			// 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)
 | 
			
		||||
 | 
			
		||||
			// Attempted withdraw of all deposited coins fails as user hasn't repaid interest debt
 | 
			
		||||
 | 
			
		||||
@ -26,4 +26,5 @@ const (
 | 
			
		||||
	AttributeKeyLiquidatedCoins        = "liquidated_coins"
 | 
			
		||||
	AttributeKeyKeeper                 = "keeper"
 | 
			
		||||
	AttributeKeyKeeperRewardCoins      = "keeper_reward_coins"
 | 
			
		||||
	AttributeKeyOwner                  = "owner"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@ -169,13 +169,15 @@ func (msg MsgBorrow) String() string {
 | 
			
		||||
// MsgRepay repays funds to the hard module.
 | 
			
		||||
type MsgRepay struct {
 | 
			
		||||
	Sender sdk.AccAddress `json:"sender" yaml:"sender"`
 | 
			
		||||
	Owner  sdk.AccAddress `json:"owner" yaml:"owner"`
 | 
			
		||||
	Amount sdk.Coins      `json:"amount" yaml:"amount"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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{
 | 
			
		||||
		Sender: sender,
 | 
			
		||||
		Owner:  owner,
 | 
			
		||||
		Amount: amount,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@ -191,6 +193,9 @@ func (msg MsgRepay) ValidateBasic() error {
 | 
			
		||||
	if msg.Sender.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() {
 | 
			
		||||
		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 {
 | 
			
		||||
	return fmt.Sprintf(`Repay Message:
 | 
			
		||||
	Sender:         %s
 | 
			
		||||
	Owner:         %s
 | 
			
		||||
	Amount:   %s
 | 
			
		||||
`, msg.Sender, msg.Amount)
 | 
			
		||||
`, msg.Sender, msg.Owner, msg.Amount)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MsgLiquidate attempts to liquidate a borrower's borrow
 | 
			
		||||
 | 
			
		||||
@ -152,6 +152,7 @@ func (suite *MsgTestSuite) TestMsgBorrow() {
 | 
			
		||||
func (suite *MsgTestSuite) TestMsgRepay() {
 | 
			
		||||
	type args struct {
 | 
			
		||||
		sender sdk.AccAddress
 | 
			
		||||
		owner  sdk.AccAddress
 | 
			
		||||
		amount sdk.Coins
 | 
			
		||||
	}
 | 
			
		||||
	addrs := []sdk.AccAddress{
 | 
			
		||||
@ -167,6 +168,7 @@ func (suite *MsgTestSuite) TestMsgRepay() {
 | 
			
		||||
			name: "valid",
 | 
			
		||||
			args: args{
 | 
			
		||||
				sender: addrs[0],
 | 
			
		||||
				owner:  addrs[0],
 | 
			
		||||
				amount: sdk.NewCoins(sdk.NewCoin("test", sdk.NewInt(1000000))),
 | 
			
		||||
			},
 | 
			
		||||
			expectPass:  true,
 | 
			
		||||
@ -175,7 +177,7 @@ func (suite *MsgTestSuite) TestMsgRepay() {
 | 
			
		||||
	}
 | 
			
		||||
	for _, tc := range testCases {
 | 
			
		||||
		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()
 | 
			
		||||
			if tc.expectPass {
 | 
			
		||||
				suite.NoError(err)
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user