Skip to content

Commit

Permalink
feat: allow user to set custom fee when paying for service creation
Browse files Browse the repository at this point in the history
  • Loading branch information
RiccardoM committed Nov 25, 2024
1 parent 03981dd commit bf092c0
Show file tree
Hide file tree
Showing 7 changed files with 300 additions and 75 deletions.
11 changes: 11 additions & 0 deletions proto/milkyway/services/v1/messages.proto
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package milkyway.services.v1;

import "amino/amino.proto";
import "cosmos_proto/cosmos.proto";
import "cosmos/base/v1beta1/coin.proto";
import "cosmos/msg/v1/msg.proto";
import "gogoproto/gogo.proto";
import "milkyway/services/v1/params.proto";
Expand Down Expand Up @@ -83,6 +84,16 @@ message MsgCreateService {
(gogoproto.moretags) = "yaml:\"picture_url\"",
(gogoproto.customname) = "PictureURL"
];

// FeeAmount represents the fees that are going to be paid to create the
// service. These should always be greater or equals of any of the coins
// specified inside the ServiceRegistrationFee field of the modules params.
// If no fees are specified inside the module parameters, this field can be
// omitted.
repeated cosmos.base.v1beta1.Coin fee_amount = 6 [
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins",
(gogoproto.nullable) = false
];
}

// MsgCreateServiceResponse is the return value of MsgCreateService.
Expand Down
9 changes: 7 additions & 2 deletions x/services/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,17 @@ func (k msgServer) CreateService(goCtx context.Context, msg *types.MsgCreateServ
}

if !params.ServiceRegistrationFee.IsZero() {
// Make sure the specified fees are enough
if !msg.FeeAmount.IsAnyGTE(params.ServiceRegistrationFee) {
return nil, errors.Wrapf(sdkerrors.ErrInsufficientFunds, "insufficient funds: %s < %s", msg.FeeAmount, params.ServiceRegistrationFee)
}

userAddress, err := sdk.AccAddressFromBech32(service.Admin)
if err != nil {
return nil, errors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid service admin address: %s", service.Admin)
return nil, errors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid operator admin address: %s", service.Admin)
}

err = k.poolKeeper.FundCommunityPool(ctx, params.ServiceRegistrationFee, userAddress)
err = k.poolKeeper.FundCommunityPool(ctx, msg.FeeAmount, userAddress)
if err != nil {
return nil, err
}
Expand Down
94 changes: 94 additions & 0 deletions x/services/keeper/msg_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ func (suite *KeeperTestSuite) TestMsgServer_CreateService() {
"MilkyWay is a restaking platform",
"https://milkyway.com",
"https://milkyway.com/logo.png",
nil,
"cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd",
),
shouldErr: true,
Expand All @@ -55,6 +56,7 @@ func (suite *KeeperTestSuite) TestMsgServer_CreateService() {
types.DoNotModify,
"https://milkyway.com",
"https://milkyway.com/logo.png",
nil,
"cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd",
),
shouldErr: true,
Expand All @@ -75,6 +77,7 @@ func (suite *KeeperTestSuite) TestMsgServer_CreateService() {
"MilkyWay is a restaking platform",
"https://milkyway.com",
"https://milkyway.com/logo.png",
nil,
"cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd",
),
shouldErr: true,
Expand All @@ -100,6 +103,7 @@ func (suite *KeeperTestSuite) TestMsgServer_CreateService() {
"MilkyWay is a restaking platform",
"https://milkyway.com",
"https://milkyway.com/logo.png",
sdk.NewCoins(sdk.NewCoin("uatom", sdkmath.NewInt(100_000))),
"cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd",
),
shouldErr: false,
Expand Down Expand Up @@ -148,6 +152,96 @@ func (suite *KeeperTestSuite) TestMsgServer_CreateService() {
suite.Require().Equal(sdk.NewCoin("uatom", sdkmath.NewInt(100_000)), poolBalance)
},
},
{
name: "service is created and fee is charged - one of many fees denoms",
store: func(ctx sdk.Context) {
err := suite.k.SetNextServiceID(ctx, 1)
suite.Require().NoError(err)

err = suite.k.SetParams(ctx, types.NewParams(
sdk.NewCoins(
sdk.NewCoin("uatom", sdkmath.NewInt(100_000_000)),
sdk.NewCoin("utia", sdkmath.NewInt(30_000_000)),
sdk.NewCoin("milktia", sdkmath.NewInt(80_000_000)),
),
))
suite.Require().NoError(err)

suite.fundAccount(ctx,
"cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd",
sdk.NewCoins(
sdk.NewCoin("uatom", sdkmath.NewInt(100_000_000)),
sdk.NewCoin("utia", sdkmath.NewInt(100_000_000)),
sdk.NewCoin("milktia", sdkmath.NewInt(100_000_000)),
),
)
},
msg: types.NewMsgCreateService(
"MilkyWay",
"MilkyWay is a restaking platform",
"https://milkyway.com",
"https://milkyway.com/logo.png",
sdk.NewCoins(
sdk.NewCoin("uatom", sdkmath.NewInt(20_000_000)),
sdk.NewCoin("utia", sdkmath.NewInt(15_000_000)),
sdk.NewCoin("milktia", sdkmath.NewInt(80_000_000)),
),
"cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd",
),
shouldErr: false,
expResponse: &types.MsgCreateServiceResponse{
NewServiceID: 1,
},
expEvents: sdk.Events{
sdk.NewEvent(
types.EventTypeCreateService,
sdk.NewAttribute(types.AttributeKeyServiceID, "1"),
),
},
check: func(ctx sdk.Context) {
// Make sure the service has been stored
stored, found, err := suite.k.GetService(ctx, 1)
suite.Require().NoError(err)
suite.Require().True(found)
suite.Require().Equal(types.NewService(
1,
types.SERVICE_STATUS_CREATED,
"MilkyWay",
"MilkyWay is a restaking platform",
"https://milkyway.com",
"https://milkyway.com/logo.png",
"cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd",
false,
), stored)

// Make sure the service account has been created properly
hasAccount := suite.ak.HasAccount(ctx, types.GetServiceAddress(1))
suite.Require().True(hasAccount)

// Make sure the next service id has been incremented
nextServiceID, err := suite.k.GetNextServiceID(ctx)
suite.Require().NoError(err)
suite.Require().Equal(uint32(2), nextServiceID)

// Make sure the user's funds were deducted
userAddress, err := sdk.AccAddressFromBech32("cosmos13t6y2nnugtshwuy0zkrq287a95lyy8vzleaxmd")
suite.Require().NoError(err)
balance := suite.bk.GetAllBalances(ctx, userAddress)
suite.Require().Equal(sdk.NewCoins(
sdk.NewCoin("uatom", sdkmath.NewInt(80_000_000)),
sdk.NewCoin("utia", sdkmath.NewInt(85_000_000)),
sdk.NewCoin("milktia", sdkmath.NewInt(20_000_000)),
), balance)

// Make sure the community pool was funded
poolBalance := suite.bk.GetAllBalances(ctx, authtypes.NewModuleAddress(distrtypes.ModuleName))
suite.Require().Equal(sdk.NewCoins(
sdk.NewCoin("uatom", sdkmath.NewInt(20_000_000)),
sdk.NewCoin("utia", sdkmath.NewInt(15_000_000)),
sdk.NewCoin("milktia", sdkmath.NewInt(80_000_000)),
), poolBalance)
},
},
}

for _, tc := range testCases {
Expand Down
19 changes: 14 additions & 5 deletions x/services/simulation/msg_factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,23 +107,31 @@ func SimulateMsgCreateService(ak authkeeper.AccountKeeper, bk bankkeeper.Keeper,
service := RandomService(r, accs)

// Get the admin account that should sign the transaction
adminAddr, err := sdk.AccAddressFromBech32(service.Admin)
adminAddress, err := sdk.AccAddressFromBech32(service.Admin)
if err != nil {
return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(msg), "error while parsing admin address"), nil, nil
}

// Make sure the admin account has enough tokens
// Make sure the admin has enough funds to pay for the creation fees
params, err := k.GetParams(ctx)
if err != nil {
return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(msg), "error while getting params"), nil, nil
return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(msg), "could not get params"), nil, nil
}

// Check the fees that should be paid
feesAmount := sdk.NewCoins()
for _, feeCoin := range params.ServiceRegistrationFee {
if bk.GetBalance(ctx, adminAddress, feeCoin.Denom).IsGTE(feeCoin) {
feesAmount = feesAmount.Add(feeCoin)
}
}

if params.ServiceRegistrationFee.IsAnyGTE(bk.GetAllBalances(ctx, adminAddr)) {
if !params.ServiceRegistrationFee.IsZero() && feesAmount.IsZero() {
return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(msg), "insufficient funds"), nil, nil
}

// Get the account that will sign the transaction
signer, found := simtesting.GetSimAccount(adminAddr, accs)
signer, found := simtesting.GetSimAccount(adminAddress, accs)
if !found {
return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(msg), "admin account not found"), nil, nil
}
Expand All @@ -134,6 +142,7 @@ func SimulateMsgCreateService(ak authkeeper.AccountKeeper, bk bankkeeper.Keeper,
service.Description,
service.Website,
service.PictureURL,
feesAmount,
service.Admin,
)
return simtesting.SendMsg(r, types.ModuleName, app, ak, bk, msg, ctx, signer)
Expand Down
14 changes: 13 additions & 1 deletion x/services/types/messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,20 @@ var (
)

// NewMsgCreateService creates a new MsgCreateService instance
func NewMsgCreateService(name, description, website, pictureURL, sender string) *MsgCreateService {
func NewMsgCreateService(
name string,
description string,
website string,
pictureURL string,
feeAmount sdk.Coins,
sender string,
) *MsgCreateService {
return &MsgCreateService{
Name: name,
Description: description,
Website: website,
PictureURL: pictureURL,
FeeAmount: feeAmount,
Sender: sender,
}
}
Expand All @@ -42,6 +50,10 @@ func (msg *MsgCreateService) ValidateBasic() error {
return errors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid picture URL")
}

if err := msg.FeeAmount.Validate(); err != nil {
return err
}

_, err := sdk.AccAddressFromBech32(msg.Sender)
if err != nil {
return errors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid sender address")
Expand Down
Loading

0 comments on commit bf092c0

Please sign in to comment.