diff --git a/x/datadeal/client/cli/tx.go b/x/datadeal/client/cli/tx.go index c9348834..dc1de363 100644 --- a/x/datadeal/client/cli/tx.go +++ b/x/datadeal/client/cli/tx.go @@ -19,5 +19,6 @@ func GetTxCmd() *cobra.Command { } cmd.AddCommand(CmdCreateDeal()) + cmd.AddCommand(CmdDeactivateDeal()) return cmd } diff --git a/x/datadeal/client/cli/txDeactivateDeal.go b/x/datadeal/client/cli/txDeactivateDeal.go new file mode 100644 index 00000000..0a8e0308 --- /dev/null +++ b/x/datadeal/client/cli/txDeactivateDeal.go @@ -0,0 +1,44 @@ +package cli + +import ( + "strconv" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/client/tx" + "github.com/medibloc/panacea-core/v2/x/datadeal/types" + "github.com/spf13/cobra" +) + +func CmdDeactivateDeal() *cobra.Command { + cmd := &cobra.Command{ + Use: "deactivate-deal [dealID]", + Short: "Deactivate a deal", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + dealID, err := strconv.ParseUint(args[0], 10, 64) + if err != nil { + return err + } + + requesterAddr := clientCtx.GetFromAddress() + + msgDeactivateDeal := types.NewMsgDeactivateDeal(dealID, requesterAddr.String()) + + if err := msgDeactivateDeal.ValidateBasic(); err != nil { + return err + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msgDeactivateDeal) + }, + } + + flags.AddTxFlagsToCmd(cmd) + + return cmd +} diff --git a/x/datadeal/keeper/certificate_test.go b/x/datadeal/keeper/certificate_test.go index a4215ab2..1d22441c 100644 --- a/x/datadeal/keeper/certificate_test.go +++ b/x/datadeal/keeper/certificate_test.go @@ -10,16 +10,20 @@ import ( cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/medibloc/panacea-core/v2/types/assets" + "github.com/medibloc/panacea-core/v2/types/testsuite" "github.com/medibloc/panacea-core/v2/x/datadeal/types" oracletypes "github.com/medibloc/panacea-core/v2/x/oracle/types" "github.com/stretchr/testify/suite" ) type certificateTestSuite struct { - dealTestSuite + testsuite.TestSuite uniqueID string + defaultFunds sdk.Coins + consumerAccAddr sdk.AccAddress + oracleAccPrivKey cryptotypes.PrivKey oracleAccPubKey cryptotypes.PubKey oracleAccAddr sdk.AccAddress diff --git a/x/datadeal/keeper/deal.go b/x/datadeal/keeper/deal.go index b5f4ef34..a9c6412d 100644 --- a/x/datadeal/keeper/deal.go +++ b/x/datadeal/keeper/deal.go @@ -160,3 +160,43 @@ func (k Keeper) IncreaseCurNumDataOfDeal(ctx sdk.Context, dealID uint64) error { } return nil } + +func (k Keeper) DeactivateDeal(ctx sdk.Context, msg *types.MsgDeactivateDeal) error { + deal, err := k.GetDeal(ctx, msg.DealId) + if err != nil { + return sdkerrors.Wrapf(types.ErrDeactivateDeal, err.Error()) + } + + if deal.ConsumerAddress != msg.RequesterAddress { + return sdkerrors.Wrapf(types.ErrDeactivateDeal, "only consumer can deactivate the deal") + } + + if deal.Status != types.DEAL_STATUS_ACTIVE { + return sdkerrors.Wrapf(types.ErrDeactivateDeal, "deal's status is not 'ACTIVE'") + } + + deal.Status = types.DEAL_STATUS_INACTIVE + + err = k.SetDeal(ctx, deal) + if err != nil { + return sdkerrors.Wrapf(types.ErrDeactivateDeal, err.Error()) + } + + dealAccAddr, err := sdk.AccAddressFromBech32(deal.Address) + if err != nil { + return sdkerrors.Wrapf(types.ErrDeactivateDeal, err.Error()) + } + + consumerAccAddr, err := sdk.AccAddressFromBech32(deal.ConsumerAddress) + if err != nil { + return sdkerrors.Wrapf(types.ErrDeactivateDeal, err.Error()) + } + + // refund a budget to consumer + err = k.bankKeeper.SendCoins(ctx, dealAccAddr, consumerAccAddr, sdk.NewCoins(*deal.Budget)) + if err != nil { + return sdkerrors.Wrapf(types.ErrDeactivateDeal, err.Error()) + } + + return nil +} diff --git a/x/datadeal/keeper/deal_test.go b/x/datadeal/keeper/deal_test.go index a1651b28..b0067107 100644 --- a/x/datadeal/keeper/deal_test.go +++ b/x/datadeal/keeper/deal_test.go @@ -16,6 +16,7 @@ type dealTestSuite struct { defaultFunds sdk.Coins consumerAccAddr sdk.AccAddress + providerAccAddr sdk.AccAddress } func TestDealTestSuite(t *testing.T) { @@ -24,6 +25,7 @@ func TestDealTestSuite(t *testing.T) { func (suite *dealTestSuite) BeforeTest(_, _ string) { suite.consumerAccAddr = sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address()) + suite.providerAccAddr = sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address()) suite.defaultFunds = sdk.NewCoins(sdk.NewCoin(assets.MicroMedDenom, sdk.NewInt(10000000000))) testDeal := suite.MakeTestDeal(1, suite.consumerAccAddr, 100) @@ -93,3 +95,71 @@ func (suite *dealTestSuite) TestCheckDealCurNumDataAndIncrement() { check = updatedDeal.IsCompleted() suite.Require().Equal(true, check) } + +func (suite *dealTestSuite) TestRequestDeactivateDeal() { + ctx := suite.Ctx + + err := suite.FundAccount(ctx, suite.consumerAccAddr, suite.defaultFunds) + suite.Require().NoError(err) + + getDeal, err := suite.DataDealKeeper.GetDeal(ctx, 1) + suite.Require().NoError(err) + + dealAccAddr, err := sdk.AccAddressFromBech32(getDeal.Address) + suite.Require().NoError(err) + + // Sending Budget from buyer to deal + err = suite.BankKeeper.SendCoins(suite.Ctx, suite.consumerAccAddr, dealAccAddr, sdk.NewCoins(*getDeal.Budget)) + suite.Require().NoError(err) + + msgDeactivateDeal := &types.MsgDeactivateDeal{ + DealId: 1, + RequesterAddress: suite.consumerAccAddr.String(), + } + + // Consumer Balance = Original Consumer Balance(10000000000umed) - Deal's Budget(1000000000umed) --> 9000000000umed + beforeConsumerBalance := suite.BankKeeper.GetBalance(suite.Ctx, suite.consumerAccAddr, assets.MicroMedDenom) + + err = suite.DataDealKeeper.DeactivateDeal(ctx, msgDeactivateDeal) + suite.Require().NoError(err) + + getDeal, err = suite.DataDealKeeper.GetDeal(ctx, 1) + suite.Require().NoError(err) + suite.Require().Equal(getDeal.Status, types.DEAL_STATUS_INACTIVE) + + // After deactivating a deal, the consumer get the refund from deal. + afterConsumerBalance := suite.BankKeeper.GetBalance(suite.Ctx, suite.consumerAccAddr, assets.MicroMedDenom) + suite.Require().Equal(beforeConsumerBalance.Add(*getDeal.Budget), afterConsumerBalance) +} + +func (suite *dealTestSuite) TestRequestDeactivateDealInvalidRequester() { + ctx := suite.Ctx + + msgDeactivateDeal := &types.MsgDeactivateDeal{ + DealId: 1, + RequesterAddress: suite.providerAccAddr.String(), + } + + err := suite.DataDealKeeper.DeactivateDeal(ctx, msgDeactivateDeal) + suite.Require().ErrorIs(err, types.ErrDeactivateDeal) +} + +func (suite *dealTestSuite) TestRequestDeactivateDealStatusNotActive() { + ctx := suite.Ctx + + getDeal, err := suite.DataDealKeeper.GetDeal(ctx, 1) + suite.Require().NoError(err) + + getDeal.Status = types.DEAL_STATUS_COMPLETED + + err = suite.DataDealKeeper.SetDeal(ctx, getDeal) + suite.Require().NoError(err) + + msgDeactivateDeal := &types.MsgDeactivateDeal{ + DealId: 1, + RequesterAddress: suite.consumerAccAddr.String(), + } + + err = suite.DataDealKeeper.DeactivateDeal(ctx, msgDeactivateDeal) + suite.Require().ErrorIs(err, types.ErrDeactivateDeal) +} diff --git a/x/datadeal/keeper/msg_server_deal.go b/x/datadeal/keeper/msg_server_deal.go index 6b21ca12..86a6a8f5 100644 --- a/x/datadeal/keeper/msg_server_deal.go +++ b/x/datadeal/keeper/msg_server_deal.go @@ -18,9 +18,15 @@ func (m msgServer) CreateDeal(goCtx context.Context, msg *types.MsgCreateDeal) ( return &types.MsgCreateDealResponse{DealId: newDealID}, nil } -func (m msgServer) DeactivateDeal(ctx context.Context, deal *types.MsgDeactivateDeal) (*types.MsgDeactivateDealResponse, error) { - //TODO implement me - panic("implement me") +func (m msgServer) DeactivateDeal(goCtx context.Context, msg *types.MsgDeactivateDeal) (*types.MsgDeactivateDealResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + err := m.Keeper.DeactivateDeal(ctx, msg) + if err != nil { + return nil, err + } + + return &types.MsgDeactivateDealResponse{}, nil } func (m msgServer) SubmitConsent(goCtx context.Context, msg *types.MsgSubmitConsent) (*types.MsgSubmitConsentResponse, error) { diff --git a/x/datadeal/types/errors.go b/x/datadeal/types/errors.go index f9439810..06779b2f 100644 --- a/x/datadeal/types/errors.go +++ b/x/datadeal/types/errors.go @@ -14,4 +14,5 @@ var ( ErrCertificateNotFound = sdkerrors.Register(ModuleName, 5, "certificate is not found") ErrGetCertificate = sdkerrors.Register(ModuleName, 6, "error while get certificate") ErrSubmitConsent = sdkerrors.Register(ModuleName, 7, "error while submit consent") + ErrDeactivateDeal = sdkerrors.Register(ModuleName, 8, "error while deactivate deal") ) diff --git a/x/datadeal/types/message_deal.go b/x/datadeal/types/message_deal.go index ace4df43..854969e6 100644 --- a/x/datadeal/types/message_deal.go +++ b/x/datadeal/types/message_deal.go @@ -84,6 +84,13 @@ func (m *MsgSubmitConsent) GetSigners() []sdk.AccAddress { var _ sdk.Msg = &MsgDeactivateDeal{} +func NewMsgDeactivateDeal(dealID uint64, requesterAddress string) *MsgDeactivateDeal { + return &MsgDeactivateDeal{ + DealId: dealID, + RequesterAddress: requesterAddress, + } +} + func (m *MsgDeactivateDeal) Route() string { return RouterKey } @@ -93,6 +100,7 @@ func (m *MsgDeactivateDeal) Type() string { } func (m *MsgDeactivateDeal) ValidateBasic() error { + if _, err := sdk.AccAddressFromBech32(m.RequesterAddress); err != nil { return sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "requesterAddress is invalid. address: %s, error: %s", m.RequesterAddress, err.Error()) }