mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-11-20 15:05:21 +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