Skip to content

Commit 4c19477

Browse files
ash-burntPeartes
andauthored
Be able to set platform send minimums (#271)
Co-authored-by: Kehinde Faleye <[email protected]>
1 parent 80609c0 commit 4c19477

File tree

12 files changed

+746
-64
lines changed

12 files changed

+746
-64
lines changed

integration_tests/minimum_fee_test.go

+63
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,16 @@ package integration_tests
22

33
import (
44
"context"
5+
"encoding/json"
56
"fmt"
7+
"strconv"
68
"testing"
9+
"time"
10+
11+
xiontypes "github.com/burnt-labs/xion/x/xion/types"
12+
"github.com/cosmos/cosmos-sdk/codec"
13+
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
14+
govv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1"
715

816
"cosmossdk.io/math"
917

@@ -97,6 +105,61 @@ func testMinimumFee(t *testing.T, td *TestData, assert assertionFn) {
97105
require.NoError(t, err)
98106
require.Equal(t, fundAmount, xionUserBalInitial)
99107

108+
cdc := codec.NewProtoCodec(xion.Config().EncodingConfig.InterfaceRegistry)
109+
config := types.GetConfig()
110+
config.SetBech32PrefixForAccount("xion", "xionpub")
111+
112+
setPlatformMinimumsMsg := xiontypes.MsgSetPlatformMinimum{
113+
Authority: authtypes.NewModuleAddress("gov").String(),
114+
Minimums: types.Coins{types.Coin{Amount: math.NewInt(10), Denom: "uxion"}},
115+
}
116+
117+
msg, err := cdc.MarshalInterfaceJSON(&setPlatformMinimumsMsg)
118+
require.NoError(t, err)
119+
120+
prop := cosmos.TxProposalv1{
121+
Messages: []json.RawMessage{msg},
122+
Metadata: "",
123+
Deposit: "100uxion",
124+
Title: "Set platform percentage to 5%",
125+
Summary: "Ups the platform fee to 5% for the integration test",
126+
}
127+
paramChangeTx, err := xion.SubmitProposal(ctx, xionUser.KeyName(), prop)
128+
require.NoError(t, err)
129+
t.Logf("Platform percentage change proposal submitted with ID %s in transaction %s", paramChangeTx.ProposalID, paramChangeTx.TxHash)
130+
131+
proposalID, err := strconv.Atoi(paramChangeTx.ProposalID)
132+
require.NoError(t, err)
133+
134+
require.Eventuallyf(t, func() bool {
135+
proposalInfo, err := xion.GovQueryProposal(ctx, uint64(proposalID))
136+
if err != nil {
137+
require.NoError(t, err)
138+
} else {
139+
if proposalInfo.Status == govv1beta1.StatusVotingPeriod {
140+
return true
141+
}
142+
t.Logf("Waiting for proposal to enter voting status VOTING, current status: %s", proposalInfo.Status)
143+
}
144+
return false
145+
}, time.Second*11, time.Second, "failed to reach status VOTING after 11s")
146+
147+
err = xion.VoteOnProposalAllValidators(ctx, uint64(proposalID), cosmos.ProposalVoteYes)
148+
require.NoError(t, err)
149+
150+
require.Eventuallyf(t, func() bool {
151+
proposalInfo, err := xion.GovQueryProposal(ctx, uint64(proposalID))
152+
if err != nil {
153+
require.NoError(t, err)
154+
} else {
155+
if proposalInfo.Status == govv1beta1.StatusPassed {
156+
return true
157+
}
158+
t.Logf("Waiting for proposal to enter voting status PASSED, current status: %s", proposalInfo.Status)
159+
}
160+
return false
161+
}, time.Second*11, time.Second, "failed to reach status PASSED after 11s")
162+
100163
// step 1: send a xion message with default (0%) platform fee
101164
recipientKeyName := "recipient-key"
102165
err = xion.CreateKey(ctx, recipientKeyName)

integration_tests/send_test.go

+66-7
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,68 @@ func TestXionSendPlatformFee(t *testing.T) {
5454
require.NoError(t, err)
5555

5656
cdc := codec.NewProtoCodec(xion.Config().EncodingConfig.InterfaceRegistry)
57+
config := types.GetConfig()
58+
config.SetBech32PrefixForAccount("xion", "xionpub")
59+
60+
setPlatformMinimumsMsg := xiontypes.MsgSetPlatformMinimum{
61+
Authority: authtypes.NewModuleAddress("gov").String(),
62+
Minimums: types.Coins{types.Coin{Amount: math.NewInt(10), Denom: "uxion"}},
63+
}
64+
65+
msg, err := cdc.MarshalInterfaceJSON(&setPlatformMinimumsMsg)
66+
require.NoError(t, err)
67+
68+
_, err = xion.GetNode().ExecTx(ctx,
69+
xionUser.KeyName(),
70+
"xion", "send", xionUser.KeyName(),
71+
"--chain-id", xion.Config().ChainID,
72+
recipientKeyAddress, fmt.Sprintf("%d%s", 100, xion.Config().Denom),
73+
)
74+
// platform minimums unset, so this should fail
75+
require.Error(t, err)
76+
77+
prop := cosmos.TxProposalv1{
78+
Messages: []json.RawMessage{msg},
79+
Metadata: "",
80+
Deposit: "100uxion",
81+
Title: "Set platform percentage to 5%",
82+
Summary: "Ups the platform fee to 5% for the integration test",
83+
}
84+
paramChangeTx, err := xion.SubmitProposal(ctx, xionUser.KeyName(), prop)
85+
require.NoError(t, err)
86+
t.Logf("Platform percentage change proposal submitted with ID %s in transaction %s", paramChangeTx.ProposalID, paramChangeTx.TxHash)
87+
88+
proposalID, err := strconv.Atoi(paramChangeTx.ProposalID)
89+
require.NoError(t, err)
90+
91+
require.Eventuallyf(t, func() bool {
92+
proposalInfo, err := xion.GovQueryProposal(ctx, uint64(proposalID))
93+
if err != nil {
94+
require.NoError(t, err)
95+
} else {
96+
if proposalInfo.Status == govv1beta1.StatusVotingPeriod {
97+
return true
98+
}
99+
t.Logf("Waiting for proposal to enter voting status VOTING, current status: %s", proposalInfo.Status)
100+
}
101+
return false
102+
}, time.Second*11, time.Second, "failed to reach status VOTING after 11s")
103+
104+
err = xion.VoteOnProposalAllValidators(ctx, uint64(proposalID), cosmos.ProposalVoteYes)
105+
require.NoError(t, err)
106+
107+
require.Eventuallyf(t, func() bool {
108+
proposalInfo, err := xion.GovQueryProposal(ctx, uint64(proposalID))
109+
if err != nil {
110+
require.NoError(t, err)
111+
} else {
112+
if proposalInfo.Status == govv1beta1.StatusPassed {
113+
return true
114+
}
115+
t.Logf("Waiting for proposal to enter voting status PASSED, current status: %s", proposalInfo.Status)
116+
}
117+
return false
118+
}, time.Second*11, time.Second, "failed to reach status PASSED after 11s")
57119

58120
_, err = xion.GetNode().ExecTx(ctx,
59121
xionUser.KeyName(),
@@ -74,29 +136,26 @@ func TestXionSendPlatformFee(t *testing.T) {
74136
"balance never correctly changed")
75137

76138
// step 2: update the platform percentage to 5%
77-
config := types.GetConfig()
78-
config.SetBech32PrefixForAccount("xion", "xionpub")
79139

80140
setPlatformPercentageMsg := xiontypes.MsgSetPlatformPercentage{
81141
Authority: authtypes.NewModuleAddress("gov").String(),
82142
PlatformPercentage: 500,
83143
}
84-
85-
msg, err := cdc.MarshalInterfaceJSON(&setPlatformPercentageMsg)
144+
msg, err = cdc.MarshalInterfaceJSON(&setPlatformPercentageMsg)
86145
require.NoError(t, err)
87146

88-
prop := cosmos.TxProposalv1{
147+
prop = cosmos.TxProposalv1{
89148
Messages: []json.RawMessage{msg},
90149
Metadata: "",
91150
Deposit: "100uxion",
92151
Title: "Set platform percentage to 5%",
93152
Summary: "Ups the platform fee to 5% for the integration test",
94153
}
95-
paramChangeTx, err := xion.SubmitProposal(ctx, xionUser.KeyName(), prop)
154+
paramChangeTx, err = xion.SubmitProposal(ctx, xionUser.KeyName(), prop)
96155
require.NoError(t, err)
97156
t.Logf("Platform percentage change proposal submitted with ID %s in transaction %s", paramChangeTx.ProposalID, paramChangeTx.TxHash)
98157

99-
proposalID, err := strconv.Atoi(paramChangeTx.ProposalID)
158+
proposalID, err = strconv.Atoi(paramChangeTx.ProposalID)
100159
require.NoError(t, err)
101160

102161
require.Eventuallyf(t, func() bool {

proto/xion/v1/genesis.proto

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
11
syntax = "proto3";
22
package xion.v1;
33

4+
import "cosmos/base/v1beta1/coin.proto";
45
import "gogoproto/gogo.proto";
56

67
option go_package = "github.com/burnt-labs/xion/x/xion/types";
78

8-
message GenesisState { uint32 platform_percentage = 1; }
9+
message GenesisState {
10+
uint32 platform_percentage = 1;
11+
repeated cosmos.base.v1beta1.Coin platform_minimums = 2 [
12+
(gogoproto.nullable) = false,
13+
(gogoproto.jsontag) = "platform_minimums,omitempty",
14+
(gogoproto.moretags) = "yaml:\"platform_minimums\"",
15+
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"
16+
];
17+
}

proto/xion/v1/tx.proto

+20
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ service Msg {
2525
// percentage fee
2626
rpc SetPlatformPercentage(MsgSetPlatformPercentage)
2727
returns (MsgSetPlatformPercentageResponse);
28+
29+
// SetPlatformMinimum defines the method for updating the platform
30+
// percentage fee
31+
rpc SetPlatformMinimum(MsgSetPlatformMinimum)
32+
returns (MsgSetPlatformMinimumResponse);
2833
}
2934

3035
// MsgSend represents a message to send coins from one account to another.
@@ -76,3 +81,18 @@ message MsgSetPlatformPercentage {
7681
}
7782

7883
message MsgSetPlatformPercentageResponse {}
84+
85+
message MsgSetPlatformMinimum {
86+
option (cosmos.msg.v1.signer) = "authority";
87+
option (amino.name) = "xion/MsgSetPlatformMinimum";
88+
89+
string authority = 1 [ (cosmos_proto.scalar) = "cosmos.AddressString" ];
90+
91+
repeated cosmos.base.v1beta1.Coin minimums = 3 [
92+
(gogoproto.nullable) = false,
93+
(amino.dont_omitempty) = true,
94+
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"
95+
];
96+
}
97+
98+
message MsgSetPlatformMinimumResponse {}

x/xion/keeper/genesis.go

+10-1
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,21 @@ import (
1111
// InitGenesis initializes the bank module's state from a given genesis state.
1212
func (k Keeper) InitGenesis(ctx sdk.Context, genState *types.GenesisState) {
1313
k.OverwritePlatformPercentage(ctx, genState.PlatformPercentage)
14+
err := k.OverwritePlatformMinimum(ctx, genState.PlatformMinimums)
15+
if err != nil {
16+
panic(err)
17+
}
1418
}
1519

1620
// ExportGenesis returns the bank module's genesis state.
1721
func (k Keeper) ExportGenesis(ctx sdk.Context) *types.GenesisState {
1822
bz := ctx.KVStore(k.storeKey).Get(types.PlatformPercentageKey)
1923
platformPercentage := binary.BigEndian.Uint32(bz)
20-
rv := types.NewGenesisState(platformPercentage)
24+
25+
platformMinimums, err := k.GetPlatformMinimums(ctx)
26+
if err != nil {
27+
panic(err)
28+
}
29+
rv := types.NewGenesisState(platformPercentage, platformMinimums)
2130
return rv
2231
}

x/xion/keeper/keeper.go

+21
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package keeper
22

33
import (
44
"context"
5+
"encoding/json"
56

67
wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types"
78

@@ -70,6 +71,26 @@ func (k Keeper) OverwritePlatformPercentage(ctx sdktypes.Context, percentage uin
7071
ctx.KVStore(k.storeKey).Set(types.PlatformPercentageKey, sdktypes.Uint64ToBigEndian(uint64(percentage)))
7172
}
7273

74+
// Platform Minimum
75+
func (k Keeper) GetPlatformMinimums(ctx sdktypes.Context) (coins sdktypes.Coins, err error) {
76+
bz := ctx.KVStore(k.storeKey).Get(types.PlatformMinimumKey)
77+
78+
if len(bz) != 0 {
79+
err = json.Unmarshal(bz, &coins)
80+
}
81+
82+
return coins, err
83+
}
84+
85+
func (k Keeper) OverwritePlatformMinimum(ctx sdktypes.Context, coins sdktypes.Coins) error {
86+
bz, err := json.Marshal(coins)
87+
if err != nil {
88+
return err
89+
}
90+
ctx.KVStore(k.storeKey).Set(types.PlatformMinimumKey, bz)
91+
return nil
92+
}
93+
7394
// Authority
7495

7596
// GetAuthority returns the x/xion module's authority.

x/xion/keeper/msg_server.go

+29-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,15 @@ func (k msgServer) Send(goCtx context.Context, msg *types.MsgSend) (*types.MsgSe
5252
}
5353

5454
percentage := k.GetPlatformPercentage(ctx)
55+
minimums, err := k.GetPlatformMinimums(ctx)
56+
if err != nil {
57+
return nil, err
58+
}
5559
throughCoins := msg.Amount
60+
if !msg.Amount.IsAnyGT(minimums) {
61+
// minimum has not been met. no coin in msg.Amount exceeds a minimum that has been set
62+
return nil, errorsmod.Wrapf(types.ErrMinimumNotMet, "received %v, needed at least %v", msg.Amount, minimums)
63+
}
5664

5765
if !percentage.IsZero() {
5866
platformCoins := msg.Amount.MulInt(percentage).QuoInt(math.NewInt(10000))
@@ -94,9 +102,18 @@ func (k msgServer) MultiSend(goCtx context.Context, msg *types.MsgMultiSend) (*t
94102
}
95103

96104
percentage := k.GetPlatformPercentage(ctx)
105+
minimums, err := k.GetPlatformMinimums(ctx)
106+
if err != nil {
107+
return nil, err
108+
}
97109
var outputs []banktypes.Output
98110
totalPlatformCoins := sdk.NewCoins()
99111

112+
if !msg.Inputs[0].Coins.IsAnyGT(minimums) {
113+
// minimum has not been met. no coin in msg.Amount exceeds a minimum that has been set
114+
return nil, errorsmod.Wrapf(types.ErrMinimumNotMet, "received %v, needed at least %v", msg.Inputs[0].Coins, minimums)
115+
}
116+
100117
for _, out := range msg.Outputs {
101118
accAddr := sdk.MustAccAddressFromBech32(out.Address)
102119

@@ -125,7 +142,7 @@ func (k msgServer) MultiSend(goCtx context.Context, msg *types.MsgMultiSend) (*t
125142
outputs = append(outputs, banktypes.NewOutput(feeCollectorAcc, totalPlatformCoins))
126143
}
127144

128-
err := k.bankKeeper.InputOutputCoins(ctx, msg.Inputs[0], outputs)
145+
err = k.bankKeeper.InputOutputCoins(ctx, msg.Inputs[0], outputs)
129146
if err != nil {
130147
return nil, err
131148
}
@@ -143,3 +160,14 @@ func (k msgServer) SetPlatformPercentage(goCtx context.Context, msg *types.MsgSe
143160

144161
return &types.MsgSetPlatformPercentageResponse{}, nil
145162
}
163+
164+
func (k msgServer) SetPlatformMinimum(goCtx context.Context, msg *types.MsgSetPlatformMinimum) (*types.MsgSetPlatformMinimumResponse, error) {
165+
if k.GetAuthority() != msg.Authority {
166+
return nil, errorsmod.Wrapf(govtypes.ErrInvalidSigner, "invalid authority; expected %s, got %s", k.GetAuthority(), msg.Authority)
167+
}
168+
169+
ctx := sdk.UnwrapSDKContext(goCtx)
170+
err := k.OverwritePlatformMinimum(ctx, msg.Minimums)
171+
172+
return &types.MsgSetPlatformMinimumResponse{}, err
173+
}

x/xion/types/errors.go

+1
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ var (
1111
ErrNoAllowedContracts = errorsmod.Register(DefaultCodespace, 2, "no contract addresses specified")
1212
ErrNoValidAllowances = errorsmod.Register(DefaultCodespace, 3, "none of the allowances accepted the msg")
1313
ErrInconsistentExpiry = errorsmod.Register(DefaultCodespace, 4, "multi allowances must all expire together")
14+
ErrMinimumNotMet = errorsmod.Register(DefaultCodespace, 5, "minimum send amount not met")
1415
)

x/xion/types/genesis.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"errors"
66

77
"github.com/cosmos/cosmos-sdk/codec"
8+
"github.com/cosmos/cosmos-sdk/types"
89
)
910

1011
// Validate performs basic validation of supply genesis data returning an
@@ -18,16 +19,17 @@ func (gs GenesisState) Validate() error {
1819
}
1920

2021
// NewGenesisState creates a new genesis state.
21-
func NewGenesisState(platformPercentage uint32) *GenesisState {
22+
func NewGenesisState(platformPercentage uint32, platformMinimums types.Coins) *GenesisState {
2223
rv := &GenesisState{
2324
PlatformPercentage: platformPercentage,
25+
PlatformMinimums: platformMinimums,
2426
}
2527
return rv
2628
}
2729

2830
// DefaultGenesisState returns a default bank module genesis state.
2931
func DefaultGenesisState() *GenesisState {
30-
return NewGenesisState(0)
32+
return NewGenesisState(0, types.Coins{})
3133
}
3234

3335
// GetGenesisStateFromAppState returns x/bank GenesisState given raw application

0 commit comments

Comments
 (0)