mirror of
				https://github.com/0glabs/0g-chain.git
				synced 2025-04-04 15:55:23 +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
	 Denali Marsh
						Denali Marsh