diff --git a/app.go b/app.go index 3342e3e8..4fbf6e04 100644 --- a/app.go +++ b/app.go @@ -404,7 +404,7 @@ func NewApp( upgrade.NewAppModule(app.upgradeKeeper), transferModule, swap.NewAppModule(app.cdc, app.swapKeeper), - vpn.NewAppModule(app.accountKeeper, app.vpnKeeper), + vpn.NewAppModule(app.cdc, app.accountKeeper, app.bankKeeper, app.vpnKeeper), ) // NOTE: order is very important here @@ -443,7 +443,7 @@ func NewApp( staking.NewAppModule(app.cdc, app.stakingKeeper, app.accountKeeper, app.bankKeeper), transferModule, swap.NewAppModule(app.cdc, app.swapKeeper), - vpn.NewAppModule(app.accountKeeper, app.vpnKeeper), + vpn.NewAppModule(app.cdc, app.accountKeeper, app.bankKeeper, app.vpnKeeper), ) app.simulationManager.RegisterStoreDecoders() diff --git a/types/address_test.go b/types/address_test.go new file mode 100644 index 00000000..2657980b --- /dev/null +++ b/types/address_test.go @@ -0,0 +1,1222 @@ +package types + +import ( + "reflect" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func TestNodeAddressFromBech32(t *testing.T) { + type args struct { + s string + } + tests := []struct { + name string + args args + want NodeAddress + wantErr bool + }{ + { + "empty", + args{ + s: "", + }, + NodeAddress{}, + true, + }, + { + "invalid", + args{ + s: "invalid", + }, + nil, + true, + }, + { + "invalid prefix", + args{ + s: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + }, + nil, + true, + }, + { + "10 bytes", + args{ + s: "sentnode1qypqxpq9qcrsszgse4wwrm", + }, + nil, + true, + }, + { + "20 bytes", + args{ + s: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + }, + NodeAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20}, + false, + }, + { + "30 bytes", + args{ + s: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqyy3zxfp9ycnjs2fsxqglcv", + }, + nil, + true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := NodeAddressFromBech32(tt.args.s) + if (err != nil) != tt.wantErr { + t.Errorf("NodeAddressFromBech32() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("NodeAddressFromBech32() got = %v, want %v", got, tt.want) + } + }) + } +} + +func TestNodeAddress_Bytes(t *testing.T) { + tests := []struct { + name string + n NodeAddress + want []byte + }{ + { + "nil", + nil, + nil, + }, + { + "empty", + NodeAddress{}, + []byte{}, + }, + { + "10 bytes", + NodeAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10}, + []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10}, + }, + { + "20 bytes", + NodeAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20}, + []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20}, + }, + { + "30 bytes", + NodeAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30}, + []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.n.Bytes(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Bytes() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestNodeAddress_Empty(t *testing.T) { + tests := []struct { + name string + n NodeAddress + want bool + }{ + { + "nil", + nil, + true}, + { + "empty", + NodeAddress{}, + true, + }, + { + "10 bytes", + NodeAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10}, + false, + }, + { + "20 bytes", + NodeAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20}, + false, + }, + { + "30 bytes", + NodeAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30}, + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.n.Empty(); got != tt.want { + t.Errorf("Empty() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestNodeAddress_Equals(t *testing.T) { + type args struct { + address sdk.Address + } + tests := []struct { + name string + n NodeAddress + args args + want bool + }{ + { + "nil", + nil, + args{ + address: nil, + }, + true, + }, + { + "equal type with 0 bytes", + NodeAddress{}, + args{ + address: NodeAddress{}, + }, + true, + }, + { + "unequal type with 0 bytes", + NodeAddress{}, + args{ + address: ProvAddress{}, + }, + true, + }, + { + "unequal type with unequal10 bytes", + NodeAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10}, + args{ + address: ProvAddress{0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20}, + }, + false, + }, + { + "unequal type with equal 10 bytes", + NodeAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10}, + args{ + address: ProvAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10}, + }, + true, + }, + { + "equal type with unequal 10 bytes", + NodeAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10}, + args{ + address: NodeAddress{0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20}, + }, + false, + }, + { + "equal type with equal 10 bytes", + NodeAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10}, + args{ + address: NodeAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10}, + }, + true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.n.Equals(tt.args.address); got != tt.want { + t.Errorf("Equals() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestNodeAddress_Marshal(t *testing.T) { + tests := []struct { + name string + n NodeAddress + want []byte + wantErr bool + }{ + { + "nil", + nil, + nil, + false, + }, + { + "empty", + NodeAddress{}, + []byte{}, + false, + }, + { + "10 bytes", + NodeAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10}, + []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10}, + false, + }, + { + "20 bytes", + NodeAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20}, + []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20}, + false, + }, + { + "30 bytes", + NodeAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30}, + []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30}, + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.n.Marshal() + if (err != nil) != tt.wantErr { + t.Errorf("Marshal() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Marshal() got = %v, want %v", got, tt.want) + } + }) + } +} + +func TestNodeAddress_MarshalJSON(t *testing.T) { + tests := []struct { + name string + n NodeAddress + want []byte + wantErr bool + }{ + { + "nil", + nil, + []byte(`""`), + false, + }, + { + "empty", + NodeAddress{}, + []byte(`""`), + false, + }, + { + "10 bytes", + NodeAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10}, + []byte(`"sentnode1qypqxpq9qcrsszgse4wwrm"`), + false, + }, + { + "20 bytes", + NodeAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20}, + []byte(`"sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey"`), + false, + }, + { + "30 bytes", + NodeAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30}, + []byte(`"sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqyy3zxfp9ycnjs2fsxqglcv"`), + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.n.MarshalJSON() + if (err != nil) != tt.wantErr { + t.Errorf("MarshalJSON() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("MarshalJSON() got = %v, want %v", got, tt.want) + } + }) + } +} + +func TestNodeAddress_MarshalYAML(t *testing.T) { + tests := []struct { + name string + n NodeAddress + want interface{} + wantErr bool + }{ + { + "nil", + nil, + "", + false, + }, + { + "empty", + NodeAddress{}, + "", + false, + }, + { + "10 bytes", + NodeAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10}, + "sentnode1qypqxpq9qcrsszgse4wwrm", + false, + }, + { + "20 bytes", + NodeAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20}, + "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + false, + }, + { + "30 bytes", + NodeAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30}, + "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqyy3zxfp9ycnjs2fsxqglcv", + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.n.MarshalYAML() + if (err != nil) != tt.wantErr { + t.Errorf("MarshalYAML() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("MarshalYAML() got = %v, want %v", got, tt.want) + } + }) + } +} + +func TestNodeAddress_String(t *testing.T) { + tests := []struct { + name string + n NodeAddress + want string + }{ + { + "nil", + nil, + "", + }, + { + "empty", + NodeAddress{}, + "", + }, + { + "10 bytes", + NodeAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10}, + "sentnode1qypqxpq9qcrsszgse4wwrm", + }, + { + "20 bytes", + NodeAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20}, + "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + }, + { + "30 bytes", + NodeAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30}, + "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqyy3zxfp9ycnjs2fsxqglcv", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.n.String(); got != tt.want { + t.Errorf("String() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestNodeAddress_Unmarshal(t *testing.T) { + type args struct { + data []byte + } + tests := []struct { + name string + n NodeAddress + args args + wantErr bool + }{ + { + "nil", + nil, + args{ + data: nil, + }, + false, + }, + { + "empty", + NodeAddress{}, + args{ + data: []byte{}, + }, + false, + }, + { + "10 bytes", + NodeAddress{}, + args{ + data: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10}, + }, + false, + }, + { + "20 bytes", + NodeAddress{}, + args{ + data: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20}, + }, + false, + }, + { + "30 bytes", + NodeAddress{}, + args{ + data: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30}, + }, + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.n.Unmarshal(tt.args.data); (err != nil) != tt.wantErr { + t.Errorf("Unmarshal() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestNodeAddress_UnmarshalJSON(t *testing.T) { + type args struct { + data []byte + } + tests := []struct { + name string + n NodeAddress + args args + wantErr bool + }{ + { + "nil", + nil, + args{ + data: []byte(`""`), + }, + true, + }, + { + "empty", + NodeAddress{}, + args{ + data: []byte(`""`), + }, + true, + }, + { + "10 bytes", + NodeAddress{}, + args{ + data: []byte(`"sentnode1qypqxpq9qcrsszgse4wwrm"`), + }, + true, + }, + { + "20 bytes", + NodeAddress{}, + args{ + data: []byte(`"sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey"`), + }, + false, + }, + { + "30 bytes", + NodeAddress{}, + args{ + data: []byte(`"sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqyy3zxfp9ycnjs2fsxqglcv"`), + }, + true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.n.UnmarshalJSON(tt.args.data); (err != nil) != tt.wantErr { + t.Errorf("UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestNodeAddress_UnmarshalYAML(t *testing.T) { + type args struct { + data []byte + } + tests := []struct { + name string + n NodeAddress + args args + wantErr bool + }{ + { + "nil", + nil, + args{ + data: nil, + }, + true, + }, + { + "empty", + NodeAddress{}, + args{ + data: []byte(""), + }, + true, + }, + { + "10 bytes", + NodeAddress{}, + args{ + data: []byte("sentnode1qypqxpq9qcrsszgse4wwrm"), + }, + true, + }, + { + "20 bytes", + NodeAddress{}, + args{ + data: []byte("sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey"), + }, + false, + }, + { + "30 bytes", + NodeAddress{}, + args{ + data: []byte("sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqyy3zxfp9ycnjs2fsxqglcv"), + }, + true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.n.UnmarshalYAML(tt.args.data); (err != nil) != tt.wantErr { + t.Errorf("UnmarshalYAML() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestProvAddressFromBech32(t *testing.T) { + type args struct { + s string + } + tests := []struct { + name string + args args + want ProvAddress + wantErr bool + }{ + { + "empty", + args{ + s: "", + }, + ProvAddress{}, + true, + }, + { + "invalid", + args{ + s: "invalid", + }, + nil, + true, + }, + { + "invalid prefix", + args{s: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + }, + nil, + true, + }, + { + "10 bytes", + args{ + s: "sentprov1qypqxpq9qcrsszgsutj8xr", + }, + nil, + true, + }, + { + "20 bytes", + args{ + s: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + }, + ProvAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20}, + false, + }, + { + "30 bytes", + args{ + s: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfqyy3zxfp9ycnjs2fsh33zgx", + }, + nil, + true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ProvAddressFromBech32(tt.args.s) + if (err != nil) != tt.wantErr { + t.Errorf("ProvAddressFromBech32() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ProvAddressFromBech32() got = %v, want %v", got, tt.want) + } + }) + } +} + +func TestProvAddress_Bytes(t *testing.T) { + tests := []struct { + name string + p ProvAddress + want []byte + }{ + { + "nil", + nil, + nil, + }, + { + "empty", + ProvAddress{}, + []byte{}, + }, + { + "10 bytes", + ProvAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10}, + []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10}, + }, + { + "20 bytes", + ProvAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20}, + []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20}, + }, + { + "30 bytes", + ProvAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30}, + []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.p.Bytes(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Bytes() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestProvAddress_Empty(t *testing.T) { + tests := []struct { + name string + p ProvAddress + want bool + }{ + { + "nil", + nil, + true, + }, + { + "empty", + ProvAddress{}, + true, + }, + { + "10 bytes", + ProvAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10}, + false, + }, + { + "20 bytes", + ProvAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20}, + false, + }, + { + "30 bytes", + ProvAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30}, + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.p.Empty(); got != tt.want { + t.Errorf("Empty() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestProvAddress_Equals(t *testing.T) { + type args struct { + address sdk.Address + } + tests := []struct { + name string + p ProvAddress + args args + want bool + }{ + { + "nil", + nil, + args{ + address: nil, + }, + true, + }, + { + "equal type with 0 bytes", + ProvAddress{}, + args{ + address: ProvAddress{}, + }, + true, + }, + { + "unequal type with 0 bytes", + ProvAddress{}, + args{ + address: NodeAddress{}, + }, + true, + }, + { + "unequal type with unequal10 bytes", + ProvAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10}, + args{ + address: NodeAddress{0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20}, + }, + false, + }, + { + "unequal type with equal 10 bytes", + ProvAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10}, + args{ + address: NodeAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10}, + }, + true, + }, + { + "equal type with unequal 10 bytes", + ProvAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10}, + args{ + address: ProvAddress{0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20}, + }, + false, + }, + { + "equal type with equal 10 bytes", + ProvAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10}, + args{ + address: ProvAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10}, + }, + true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.p.Equals(tt.args.address); got != tt.want { + t.Errorf("Equals() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestProvAddress_Marshal(t *testing.T) { + tests := []struct { + name string + p ProvAddress + want []byte + wantErr bool + }{ + { + "nil", + nil, + nil, + false, + }, + { + "empty", + ProvAddress{}, + []byte{}, + false, + }, + { + "10 bytes", + ProvAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10}, + []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10}, + false, + }, + { + "20 bytes", + ProvAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20}, + []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20}, + false, + }, + { + "30 bytes", + ProvAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30}, + []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30}, + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.p.Marshal() + if (err != nil) != tt.wantErr { + t.Errorf("Marshal() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Marshal() got = %v, want %v", got, tt.want) + } + }) + } +} + +func TestProvAddress_MarshalJSON(t *testing.T) { + tests := []struct { + name string + p ProvAddress + want []byte + wantErr bool + }{ + { + "nil", + nil, + []byte(`""`), + false, + }, + { + "empty", + ProvAddress{}, + []byte(`""`), + false, + }, + { + "10 bytes", + ProvAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10}, + []byte(`"sentprov1qypqxpq9qcrsszgsutj8xr"`), + false, + }, + { + "20 bytes", + ProvAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20}, + []byte(`"sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82"`), + false, + }, + { + "30 bytes", + ProvAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30}, + []byte(`"sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfqyy3zxfp9ycnjs2fsh33zgx"`), + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.p.MarshalJSON() + if (err != nil) != tt.wantErr { + t.Errorf("MarshalJSON() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("MarshalJSON() got = %v, want %v", got, tt.want) + } + }) + } +} + +func TestProvAddress_MarshalYAML(t *testing.T) { + tests := []struct { + name string + p ProvAddress + want interface{} + wantErr bool + }{ + { + "nil", + nil, + "", + false, + }, + { + "empty", + ProvAddress{}, + "", + false, + }, + { + "10 bytes", + ProvAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10}, + "sentprov1qypqxpq9qcrsszgsutj8xr", + false, + }, + { + "20 bytes", + ProvAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20}, + "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + false, + }, + { + "30 bytes", + ProvAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30}, + "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfqyy3zxfp9ycnjs2fsh33zgx", + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.p.MarshalYAML() + if (err != nil) != tt.wantErr { + t.Errorf("MarshalYAML() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("MarshalYAML() got = %v, want %v", got, tt.want) + } + }) + } +} + +func TestProvAddress_String(t *testing.T) { + tests := []struct { + name string + p ProvAddress + want string + }{ + { + "nil", + nil, + "", + }, + { + "empty", + ProvAddress{}, + "", + }, + { + "10 bytes", + ProvAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10}, + "sentprov1qypqxpq9qcrsszgsutj8xr", + }, + { + "20 bytes", + ProvAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20}, + "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + }, + { + "30 bytes", + ProvAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30}, + "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfqyy3zxfp9ycnjs2fsh33zgx", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.p.String(); got != tt.want { + t.Errorf("String() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestProvAddress_Unmarshal(t *testing.T) { + type args struct { + data []byte + } + tests := []struct { + name string + p ProvAddress + args args + wantErr bool + }{ + { + "nil", + nil, + args{ + data: nil, + }, + false, + }, + { + "empty", + ProvAddress{}, + args{ + data: []byte{}, + }, + false, + }, + { + "10 bytes", + ProvAddress{}, + args{ + data: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10}, + }, + false, + }, + { + "20 bytes", + ProvAddress{}, + args{ + data: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20}, + }, + false, + }, + { + "30 bytes", + ProvAddress{}, + args{ + data: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30}, + }, + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.p.Unmarshal(tt.args.data); (err != nil) != tt.wantErr { + t.Errorf("Unmarshal() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestProvAddress_UnmarshalJSON(t *testing.T) { + type args struct { + data []byte + } + tests := []struct { + name string + p ProvAddress + args args + wantErr bool + }{ + { + "nil", + nil, + args{ + data: []byte(`""`), + }, + true, + }, + { + "empty", + ProvAddress{}, + args{ + data: []byte(`""`), + }, + true, + }, + { + "10 bytes", + ProvAddress{}, + args{ + data: []byte(`"sentprov1qypqxpq9qcrsszgsutj8xr"`), + }, + true, + }, + { + "20 bytes", + ProvAddress{}, + args{ + data: []byte(`"sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82"`), + }, + false, + }, + { + "30 bytes", + ProvAddress{}, + args{ + data: []byte(`"sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfqyy3zxfp9ycnjs2fsh33zgx"`), + }, + true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.p.UnmarshalJSON(tt.args.data); (err != nil) != tt.wantErr { + t.Errorf("UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestProvAddress_UnmarshalYAML(t *testing.T) { + type args struct { + data []byte + } + tests := []struct { + name string + p ProvAddress + args args + wantErr bool + }{ + { + "nil", + nil, + args{ + data: nil, + }, + true, + }, + { + "empty", + ProvAddress{}, + args{ + data: []byte(""), + }, + true, + }, + { + "10 bytes", + ProvAddress{}, + args{ + data: []byte("sentprov1qypqxpq9qcrsszgsutj8xr"), + }, + true, + }, + { + "20 bytes", + ProvAddress{}, + args{ + data: []byte("sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82"), + }, + false, + }, + { + "30 bytes", + ProvAddress{}, + args{ + data: []byte("sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfqyy3zxfp9ycnjs2fsh33zgx"), + }, + true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.p.UnmarshalYAML(tt.args.data); (err != nil) != tt.wantErr { + t.Errorf("UnmarshalYAML() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/types/bandwidth_test.go b/types/bandwidth_test.go new file mode 100644 index 00000000..a3bf798b --- /dev/null +++ b/types/bandwidth_test.go @@ -0,0 +1,1067 @@ +package types + +import ( + "reflect" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func TestBandwidth_Add(t *testing.T) { + type fields struct { + Upload sdk.Int + Download sdk.Int + } + type args struct { + v Bandwidth + } + tests := []struct { + name string + fields fields + args args + want Bandwidth + }{ + { + "negative upload and negative download", + fields{ + Upload: sdk.NewInt(0), + Download: sdk.NewInt(0), + }, + args{ + Bandwidth{Upload: sdk.NewInt(-1000), Download: sdk.NewInt(-1000)}, + }, + Bandwidth{Upload: sdk.NewInt(-1000), Download: sdk.NewInt(-1000)}, + }, + { + "negative upload and zero download", + fields{ + Upload: sdk.NewInt(0), + Download: sdk.NewInt(0), + }, + args{ + Bandwidth{Upload: sdk.NewInt(-1000), Download: sdk.NewInt(0)}, + }, + Bandwidth{Upload: sdk.NewInt(-1000), Download: sdk.NewInt(0)}, + }, + { + "negative upload and positive download", + fields{ + Upload: sdk.NewInt(0), + Download: sdk.NewInt(0), + }, + args{ + Bandwidth{Upload: sdk.NewInt(-1000), Download: sdk.NewInt(1000)}, + }, + Bandwidth{Upload: sdk.NewInt(-1000), Download: sdk.NewInt(1000)}, + }, + { + "zero upload and negative download", + fields{ + Upload: sdk.NewInt(0), + Download: sdk.NewInt(0), + }, + args{ + Bandwidth{Upload: sdk.NewInt(0), Download: sdk.NewInt(-1000)}, + }, + Bandwidth{Upload: sdk.NewInt(0), Download: sdk.NewInt(-1000)}, + }, + { + "zero upload and zero download", + fields{ + Upload: sdk.NewInt(0), + Download: sdk.NewInt(0), + }, + args{ + Bandwidth{Upload: sdk.NewInt(0), Download: sdk.NewInt(0)}, + }, + Bandwidth{Upload: sdk.NewInt(0), Download: sdk.NewInt(0)}, + }, + { + "zero upload and positive download", + fields{ + Upload: sdk.NewInt(0), + Download: sdk.NewInt(0), + }, + args{ + Bandwidth{Upload: sdk.NewInt(0), Download: sdk.NewInt(1000)}, + }, + Bandwidth{Upload: sdk.NewInt(0), Download: sdk.NewInt(1000)}, + }, + { + "positive upload and negative download", + fields{ + Upload: sdk.NewInt(0), + Download: sdk.NewInt(0), + }, + args{ + Bandwidth{Upload: sdk.NewInt(1000), Download: sdk.NewInt(-1000)}, + }, + Bandwidth{Upload: sdk.NewInt(1000), Download: sdk.NewInt(-1000)}, + }, + { + "positive upload and zero download", + fields{ + Upload: sdk.NewInt(0), + Download: sdk.NewInt(0), + }, + args{ + Bandwidth{Upload: sdk.NewInt(1000), Download: sdk.NewInt(0)}, + }, + Bandwidth{Upload: sdk.NewInt(1000), Download: sdk.NewInt(0)}, + }, + { + "positive upload and positive download", + fields{ + Upload: sdk.NewInt(0), + Download: sdk.NewInt(0), + }, + args{ + Bandwidth{Upload: sdk.NewInt(1000), Download: sdk.NewInt(1000)}, + }, + Bandwidth{Upload: sdk.NewInt(1000), Download: sdk.NewInt(1000)}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b := Bandwidth{ + Upload: tt.fields.Upload, + Download: tt.fields.Download, + } + if got := b.Add(tt.args.v); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Add() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestBandwidth_CeilTo(t *testing.T) { + type fields struct { + Upload sdk.Int + Download sdk.Int + } + type args struct { + precision sdk.Int + } + tests := []struct { + name string + fields fields + args args + want Bandwidth + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b := Bandwidth{ + Upload: tt.fields.Upload, + Download: tt.fields.Download, + } + if got := b.CeilTo(tt.args.precision); !reflect.DeepEqual(got, tt.want) { + t.Errorf("CeilTo() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestBandwidth_IsAllLTE(t *testing.T) { + type fields struct { + Upload sdk.Int + Download sdk.Int + } + type args struct { + v Bandwidth + } + tests := []struct { + name string + fields fields + args args + want bool + }{ + { + "negative upload and negative download", + fields{ + Upload: sdk.NewInt(0), + Download: sdk.NewInt(0), + }, + args{ + Bandwidth{Upload: sdk.NewInt(-1000), Download: sdk.NewInt(-1000)}, + }, + false, + }, + { + "negative upload and zero download", + fields{ + Upload: sdk.NewInt(0), + Download: sdk.NewInt(0), + }, + args{ + Bandwidth{Upload: sdk.NewInt(-1000), Download: sdk.NewInt(0)}, + }, + false, + }, + { + "negative upload and positive download", + fields{ + Upload: sdk.NewInt(0), + Download: sdk.NewInt(0), + }, + args{ + Bandwidth{Upload: sdk.NewInt(-1000), Download: sdk.NewInt(1000)}, + }, + false, + }, + { + "zero upload and negative download", + fields{ + Upload: sdk.NewInt(0), + Download: sdk.NewInt(0), + }, + args{ + Bandwidth{Upload: sdk.NewInt(0), Download: sdk.NewInt(-1000)}, + }, + false, + }, + { + "zero upload and zero download", + fields{ + Upload: sdk.NewInt(0), + Download: sdk.NewInt(0), + }, + args{ + Bandwidth{Upload: sdk.NewInt(0), Download: sdk.NewInt(0)}, + }, + true, + }, + { + "zero upload and positive download", + fields{ + Upload: sdk.NewInt(0), + Download: sdk.NewInt(0), + }, + args{ + Bandwidth{Upload: sdk.NewInt(0), Download: sdk.NewInt(1000)}, + }, + true, + }, + { + "positive upload and negative download", + fields{ + Upload: sdk.NewInt(0), + Download: sdk.NewInt(0), + }, + args{ + Bandwidth{Upload: sdk.NewInt(1000), Download: sdk.NewInt(-1000)}, + }, + false, + }, + { + "positive upload and zero download", + fields{ + Upload: sdk.NewInt(0), + Download: sdk.NewInt(0), + }, + args{ + Bandwidth{Upload: sdk.NewInt(1000), Download: sdk.NewInt(0)}, + }, + true, + }, + { + "positive upload and positive download", + fields{ + Upload: sdk.NewInt(0), + Download: sdk.NewInt(0), + }, + args{ + Bandwidth{Upload: sdk.NewInt(1000), Download: sdk.NewInt(1000)}, + }, + true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b := Bandwidth{ + Upload: tt.fields.Upload, + Download: tt.fields.Download, + } + if got := b.IsAllLTE(tt.args.v); got != tt.want { + t.Errorf("IsAllLTE() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestBandwidth_IsAllPositive(t *testing.T) { + type fields struct { + Upload sdk.Int + Download sdk.Int + } + tests := []struct { + name string + fields fields + want bool + }{ + { + "negative upload and negative download", + fields{ + Upload: sdk.NewInt(-1000), + Download: sdk.NewInt(-1000), + }, + false, + }, + { + "negative upload and zero download", + fields{ + Upload: sdk.NewInt(-1000), + Download: sdk.NewInt(0), + }, + false, + }, + { + "negative upload and positive download", + fields{ + Upload: sdk.NewInt(-1000), + Download: sdk.NewInt(1000), + }, + false, + }, + { + "zero upload and negative download", + fields{ + Upload: sdk.NewInt(0), + Download: sdk.NewInt(-1000), + }, + false, + }, + { + "zero upload and zero download", + fields{ + Upload: sdk.NewInt(0), + Download: sdk.NewInt(0), + }, + false, + }, + { + "zero upload and positive download", + fields{ + Upload: sdk.NewInt(0), + Download: sdk.NewInt(1000), + }, + false, + }, + { + "positive upload and negative download", + fields{ + Upload: sdk.NewInt(1000), + Download: sdk.NewInt(-1000), + }, + false, + }, + { + "positive upload and zero download", + fields{ + Upload: sdk.NewInt(1000), + Download: sdk.NewInt(0), + }, + false, + }, + { + "positive upload and positive download", + fields{ + Upload: sdk.NewInt(1000), + Download: sdk.NewInt(1000), + }, + true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b := Bandwidth{ + Upload: tt.fields.Upload, + Download: tt.fields.Download, + } + if got := b.IsAllPositive(); got != tt.want { + t.Errorf("IsAllPositive() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestBandwidth_IsAllZero(t *testing.T) { + type fields struct { + Upload sdk.Int + Download sdk.Int + } + tests := []struct { + name string + fields fields + want bool + }{ + { + "negative upload and negative download", + fields{ + Upload: sdk.NewInt(-1000), + Download: sdk.NewInt(-1000), + }, + false, + }, + { + "negative upload and zero download", + fields{ + Upload: sdk.NewInt(-1000), + Download: sdk.NewInt(0), + }, + false, + }, + { + "negative upload and positive download", + fields{ + Upload: sdk.NewInt(-1000), + Download: sdk.NewInt(1000), + }, + false, + }, + { + "zero upload and negative download", + fields{ + Upload: sdk.NewInt(0), + Download: sdk.NewInt(-1000), + }, + false, + }, + { + "zero upload and zero download", + fields{ + Upload: sdk.NewInt(0), + Download: sdk.NewInt(0), + }, + true, + }, + { + "zero upload and positive download", + fields{ + Upload: sdk.NewInt(0), + Download: sdk.NewInt(1000), + }, + false, + }, + { + "positive upload and negative download", + fields{ + Upload: sdk.NewInt(1000), + Download: sdk.NewInt(-1000), + }, + false, + }, + { + "positive upload and zero download", + fields{ + Upload: sdk.NewInt(1000), + Download: sdk.NewInt(0), + }, + false, + }, + { + "positive upload and positive download", + fields{ + Upload: sdk.NewInt(1000), + Download: sdk.NewInt(1000), + }, + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b := Bandwidth{ + Upload: tt.fields.Upload, + Download: tt.fields.Download, + } + if got := b.IsAllZero(); got != tt.want { + t.Errorf("IsAllZero() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestBandwidth_IsAnyGT(t *testing.T) { + type fields struct { + Upload sdk.Int + Download sdk.Int + } + type args struct { + v Bandwidth + } + tests := []struct { + name string + fields fields + args args + want bool + }{ + { + "negative upload and negative download", + fields{ + Upload: sdk.NewInt(0), + Download: sdk.NewInt(0), + }, + args{ + Bandwidth{Upload: sdk.NewInt(-1000), Download: sdk.NewInt(-1000)}, + }, + true, + }, + { + "negative upload and zero download", + fields{ + Upload: sdk.NewInt(0), + Download: sdk.NewInt(0), + }, + args{ + Bandwidth{Upload: sdk.NewInt(-1000), Download: sdk.NewInt(0)}, + }, + true, + }, + { + "negative upload and positive download", + fields{ + Upload: sdk.NewInt(0), + Download: sdk.NewInt(0), + }, + args{ + Bandwidth{Upload: sdk.NewInt(-1000), Download: sdk.NewInt(1000)}, + }, + true, + }, + { + "zero upload and negative download", + fields{ + Upload: sdk.NewInt(0), + Download: sdk.NewInt(0), + }, + args{ + Bandwidth{Upload: sdk.NewInt(0), Download: sdk.NewInt(-1000)}, + }, + true, + }, + { + "zero upload and zero download", + fields{ + Upload: sdk.NewInt(0), + Download: sdk.NewInt(0), + }, + args{ + Bandwidth{Upload: sdk.NewInt(0), Download: sdk.NewInt(0)}, + }, + false, + }, + { + "zero upload and positive download", + fields{ + Upload: sdk.NewInt(0), + Download: sdk.NewInt(0), + }, + args{ + Bandwidth{Upload: sdk.NewInt(0), Download: sdk.NewInt(1000)}, + }, + false, + }, + { + "positive upload and negative download", + fields{ + Upload: sdk.NewInt(0), + Download: sdk.NewInt(0), + }, + args{ + Bandwidth{Upload: sdk.NewInt(1000), Download: sdk.NewInt(-1000)}, + }, + true, + }, + { + "positive upload and zero download", + fields{ + Upload: sdk.NewInt(0), + Download: sdk.NewInt(0), + }, + args{ + Bandwidth{Upload: sdk.NewInt(1000), Download: sdk.NewInt(0)}, + }, + false, + }, + { + "positive upload and positive download", + fields{ + Upload: sdk.NewInt(0), + Download: sdk.NewInt(0), + }, + args{ + Bandwidth{Upload: sdk.NewInt(1000), Download: sdk.NewInt(1000)}, + }, + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b := Bandwidth{ + Upload: tt.fields.Upload, + Download: tt.fields.Download, + } + if got := b.IsAnyGT(tt.args.v); got != tt.want { + t.Errorf("IsAnyGT() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestBandwidth_IsAnyNegative(t *testing.T) { + type fields struct { + Upload sdk.Int + Download sdk.Int + } + tests := []struct { + name string + fields fields + want bool + }{ + { + "negative upload and negative download", + fields{ + Upload: sdk.NewInt(-1000), + Download: sdk.NewInt(-1000), + }, + true, + }, + { + "negative upload and zero download", + fields{ + Upload: sdk.NewInt(-1000), + Download: sdk.NewInt(0), + }, + true, + }, + { + "negative upload and positive download", + fields{ + Upload: sdk.NewInt(-1000), + Download: sdk.NewInt(1000), + }, + true, + }, + { + "zero upload and negative download", + fields{ + Upload: sdk.NewInt(0), + Download: sdk.NewInt(-1000), + }, + true, + }, + { + "zero upload and zero download", + fields{ + Upload: sdk.NewInt(0), + Download: sdk.NewInt(0), + }, + false, + }, + { + "zero upload and positive download", + fields{ + Upload: sdk.NewInt(0), + Download: sdk.NewInt(1000), + }, + false, + }, + { + "positive upload and negative download", + fields{ + Upload: sdk.NewInt(1000), + Download: sdk.NewInt(-1000), + }, + true, + }, + { + "positive upload and zero download", + fields{ + Upload: sdk.NewInt(1000), + Download: sdk.NewInt(0), + }, + false, + }, + { + "positive upload and positive download", + fields{ + Upload: sdk.NewInt(1000), + Download: sdk.NewInt(1000), + }, + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b := Bandwidth{ + Upload: tt.fields.Upload, + Download: tt.fields.Download, + } + if got := b.IsAnyNegative(); got != tt.want { + t.Errorf("IsAnyNegative() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestBandwidth_IsAnyZero(t *testing.T) { + type fields struct { + Upload sdk.Int + Download sdk.Int + } + tests := []struct { + name string + fields fields + want bool + }{ + { + "negative upload and negative download", + fields{ + Upload: sdk.NewInt(-1000), + Download: sdk.NewInt(-1000), + }, + false, + }, + { + "negative upload and zero download", + fields{ + Upload: sdk.NewInt(-1000), + Download: sdk.NewInt(0), + }, + true, + }, + { + "negative upload and positive download", + fields{ + Upload: sdk.NewInt(-1000), + Download: sdk.NewInt(1000), + }, + false, + }, + { + "zero upload and negative download", + fields{ + Upload: sdk.NewInt(0), + Download: sdk.NewInt(-1000), + }, + true, + }, + { + "zero upload and zero download", + fields{ + Upload: sdk.NewInt(0), + Download: sdk.NewInt(0), + }, + true, + }, + { + "zero upload and positive download", + fields{ + Upload: sdk.NewInt(0), + Download: sdk.NewInt(1000), + }, + true, + }, + { + "positive upload and negative download", + fields{ + Upload: sdk.NewInt(1000), + Download: sdk.NewInt(-1000), + }, + false, + }, + { + "positive upload and zero download", + fields{ + Upload: sdk.NewInt(1000), + Download: sdk.NewInt(0), + }, + true, + }, + { + "positive upload and positive download", + fields{ + Upload: sdk.NewInt(1000), + Download: sdk.NewInt(1000), + }, + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b := Bandwidth{ + Upload: tt.fields.Upload, + Download: tt.fields.Download, + } + if got := b.IsAnyZero(); got != tt.want { + t.Errorf("IsAnyZero() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestBandwidth_Sub(t *testing.T) { + type fields struct { + Upload sdk.Int + Download sdk.Int + } + type args struct { + v Bandwidth + } + tests := []struct { + name string + fields fields + args args + want Bandwidth + }{ + { + "negative upload and negative download", + fields{ + Upload: sdk.NewInt(0), + Download: sdk.NewInt(0), + }, + args{ + Bandwidth{Upload: sdk.NewInt(-1000), Download: sdk.NewInt(-1000)}, + }, + Bandwidth{Upload: sdk.NewInt(1000), Download: sdk.NewInt(1000)}, + }, + { + "negative upload and zero download", + fields{ + Upload: sdk.NewInt(0), + Download: sdk.NewInt(0), + }, + args{ + Bandwidth{Upload: sdk.NewInt(-1000), Download: sdk.NewInt(0)}, + }, + Bandwidth{Upload: sdk.NewInt(1000), Download: sdk.NewInt(0)}, + }, + { + "negative upload and positive download", + fields{ + Upload: sdk.NewInt(0), + Download: sdk.NewInt(0), + }, + args{ + Bandwidth{Upload: sdk.NewInt(-1000), Download: sdk.NewInt(1000)}, + }, + Bandwidth{Upload: sdk.NewInt(1000), Download: sdk.NewInt(-1000)}, + }, + { + "zero upload and negative download", + fields{ + Upload: sdk.NewInt(0), + Download: sdk.NewInt(0), + }, + args{ + Bandwidth{Upload: sdk.NewInt(0), Download: sdk.NewInt(-1000)}, + }, + Bandwidth{Upload: sdk.NewInt(0), Download: sdk.NewInt(1000)}, + }, + { + "zero upload and zero download", + fields{ + Upload: sdk.NewInt(0), + Download: sdk.NewInt(0), + }, + args{ + Bandwidth{Upload: sdk.NewInt(0), Download: sdk.NewInt(0)}, + }, + Bandwidth{Upload: sdk.NewInt(0), Download: sdk.NewInt(0)}, + }, + { + "zero upload and positive download", + fields{ + Upload: sdk.NewInt(0), + Download: sdk.NewInt(0), + }, + args{ + Bandwidth{Upload: sdk.NewInt(0), Download: sdk.NewInt(1000)}, + }, + Bandwidth{Upload: sdk.NewInt(0), Download: sdk.NewInt(-1000)}, + }, + { + "positive upload and negative download", + fields{ + Upload: sdk.NewInt(0), + Download: sdk.NewInt(0), + }, + args{ + Bandwidth{Upload: sdk.NewInt(1000), Download: sdk.NewInt(-1000)}, + }, + Bandwidth{Upload: sdk.NewInt(-1000), Download: sdk.NewInt(1000)}, + }, + { + "positive upload and zero download", + fields{ + Upload: sdk.NewInt(0), + Download: sdk.NewInt(0), + }, + args{ + Bandwidth{Upload: sdk.NewInt(1000), Download: sdk.NewInt(0)}, + }, + Bandwidth{Upload: sdk.NewInt(-1000), Download: sdk.NewInt(0)}, + }, + { + "positive upload and positive download", + fields{ + Upload: sdk.NewInt(0), + Download: sdk.NewInt(0), + }, + args{ + Bandwidth{Upload: sdk.NewInt(1000), Download: sdk.NewInt(1000)}, + }, + Bandwidth{Upload: sdk.NewInt(-1000), Download: sdk.NewInt(-1000)}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b := Bandwidth{ + Upload: tt.fields.Upload, + Download: tt.fields.Download, + } + if got := b.Sub(tt.args.v); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Sub() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestBandwidth_Sum(t *testing.T) { + type fields struct { + Upload sdk.Int + Download sdk.Int + } + tests := []struct { + name string + fields fields + want sdk.Int + }{ + { + "negative upload and negative download", + fields{ + Upload: sdk.NewInt(-1000), + Download: sdk.NewInt(-1000), + }, + sdk.NewInt(-2000), + }, + { + "negative upload and zero download", + fields{ + Upload: sdk.NewInt(-1000), + Download: sdk.NewInt(0), + }, + sdk.NewInt(-1000), + }, + // { + // "negative upload and positive download", + // fields{ + // Upload: sdk.NewInt(-1000), + // Download: sdk.NewInt(1000), + // }, + // sdk.NewInt(0), + // }, + { + "zero upload and negative download", + fields{ + Upload: sdk.NewInt(0), + Download: sdk.NewInt(-1000), + }, + sdk.NewInt(-1000), + }, + { + "zero upload and zero download", + fields{ + Upload: sdk.NewInt(0), + Download: sdk.NewInt(0), + }, + sdk.NewInt(0), + }, + { + "zero upload and positive download", + fields{ + Upload: sdk.NewInt(0), + Download: sdk.NewInt(1000), + }, + sdk.NewInt(1000), + }, + // { + // "positive upload and negative download", + // fields{ + // Upload: sdk.NewInt(1000), + // Download: sdk.NewInt(-1000), + // }, + // sdk.NewInt(0), + // }, + { + "positive upload and zero download", + fields{ + Upload: sdk.NewInt(1000), + Download: sdk.NewInt(0), + }, + sdk.NewInt(1000), + }, + { + "positive upload and positive download", + fields{ + Upload: sdk.NewInt(1000), + Download: sdk.NewInt(1000), + }, + sdk.NewInt(2000), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b := Bandwidth{ + Upload: tt.fields.Upload, + Download: tt.fields.Download, + } + if got := b.Sum(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Sum() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestNewBandwidth(t *testing.T) { + type args struct { + upload sdk.Int + download sdk.Int + } + tests := []struct { + name string + args args + want Bandwidth + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := NewBandwidth(tt.args.upload, tt.args.download); !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewBandwidth() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestNewBandwidthFromInt64(t *testing.T) { + type args struct { + upload int64 + download int64 + } + tests := []struct { + name string + args args + want Bandwidth + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := NewBandwidthFromInt64(tt.args.upload, tt.args.download); !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewBandwidthFromInt64() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/types/config_test.go b/types/config_test.go new file mode 100644 index 00000000..d75b78ed --- /dev/null +++ b/types/config_test.go @@ -0,0 +1,200 @@ +package types + +import ( + "sync" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func TestConfig_GetBech32NodeAddrPrefix(t *testing.T) { + type fields struct { + Config *sdk.Config + prefixes map[string]string + sealed bool + mtx sync.Mutex + } + tests := []struct { + name string + fields fields + want string + }{ + { + "invalid prefix", + fields{ + Config: sdk.GetConfig(), + prefixes: map[string]string{"node_addr": "sent"}, + sealed: false, + mtx: sync.Mutex{}, + }, + "sent", + }, + { + "valid prefix", + fields{ + Config: sdk.GetConfig(), + prefixes: map[string]string{"node_addr": "sentnode"}, + sealed: false, + mtx: sync.Mutex{}, + }, + "sentnode", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &Config{ + Config: tt.fields.Config, + prefixes: tt.fields.prefixes, + sealed: tt.fields.sealed, + mtx: tt.fields.mtx, + } + if got := c.GetBech32NodeAddrPrefix(); got != tt.want { + t.Errorf("GetBech32NodeAddrPrefix() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestConfig_GetBech32NodePubPrefix(t *testing.T) { + type fields struct { + Config *sdk.Config + prefixes map[string]string + sealed bool + mtx sync.Mutex + } + tests := []struct { + name string + fields fields + want string + }{ + { + "invalid prefix", + fields{ + Config: sdk.GetConfig(), + prefixes: map[string]string{"node_pub": "sentpub"}, + sealed: false, + mtx: sync.Mutex{}, + }, + "sentpub", + }, + { + "valid prefix", + fields{ + Config: sdk.GetConfig(), + prefixes: map[string]string{"node_pub": "sentnodepub"}, + sealed: false, + mtx: sync.Mutex{}, + }, + "sentnodepub", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &Config{ + Config: tt.fields.Config, + prefixes: tt.fields.prefixes, + sealed: tt.fields.sealed, + mtx: tt.fields.mtx, + } + if got := c.GetBech32NodePubPrefix(); got != tt.want { + t.Errorf("GetBech32NodePubPrefix() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestConfig_GetBech32ProviderAddrPrefix(t *testing.T) { + type fields struct { + Config *sdk.Config + prefixes map[string]string + sealed bool + mtx sync.Mutex + } + tests := []struct { + name string + fields fields + want string + }{ + { + "invalid prefix", + fields{ + Config: sdk.GetConfig(), + prefixes: map[string]string{"provider_addr": "sent"}, + sealed: false, + mtx: sync.Mutex{}, + }, + "sent", + }, + { + "valid prefix", + fields{ + Config: sdk.GetConfig(), + prefixes: map[string]string{"provider_addr": "sentprov"}, + sealed: false, + mtx: sync.Mutex{}, + }, + "sentprov", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &Config{ + Config: tt.fields.Config, + prefixes: tt.fields.prefixes, + sealed: tt.fields.sealed, + mtx: tt.fields.mtx, + } + if got := c.GetBech32ProviderAddrPrefix(); got != tt.want { + t.Errorf("GetBech32ProviderAddrPrefix() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestConfig_GetBech32ProviderPubPrefix(t *testing.T) { + type fields struct { + Config *sdk.Config + prefixes map[string]string + sealed bool + mtx sync.Mutex + } + tests := []struct { + name string + fields fields + want string + }{ + { + "invalid prefix", + fields{ + Config: sdk.GetConfig(), + prefixes: map[string]string{"provider_pub": "sentpub"}, + sealed: false, + mtx: sync.Mutex{}, + }, + "sentpub", + }, + { + "valid prefix", + fields{ + Config: sdk.GetConfig(), + prefixes: map[string]string{"provider_pub": "sentprovpub"}, + sealed: false, + mtx: sync.Mutex{}, + }, + "sentprovpub", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &Config{ + Config: tt.fields.Config, + prefixes: tt.fields.prefixes, + sealed: tt.fields.sealed, + mtx: tt.fields.mtx, + } + if got := c.GetBech32ProviderPubPrefix(); got != tt.want { + t.Errorf("GetBech32ProviderPubPrefix() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/types/simulation/coin.go b/types/simulation/coin.go new file mode 100644 index 00000000..f0958176 --- /dev/null +++ b/types/simulation/coin.go @@ -0,0 +1,30 @@ +package simulation + +import ( + "math/rand" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func RandomCoin(r *rand.Rand, coin sdk.Coin) sdk.Coin { + return sdk.NewInt64Coin( + coin.Denom, + r.Int63n(coin.Amount.Int64()), + ) +} + +func RandomCoins(r *rand.Rand, coins sdk.Coins) sdk.Coins { + if len(coins) == 0 { + return nil + } + + items := make(sdk.Coins, 0, r.Intn(len(coins))) + for _, coin := range coins { + items = append( + items, + RandomCoin(r, coin), + ) + } + + return items +} diff --git a/types/status_test.go b/types/status_test.go new file mode 100644 index 00000000..fd8bb275 --- /dev/null +++ b/types/status_test.go @@ -0,0 +1,250 @@ +package types + +import ( + "testing" +) + +func TestStatusFromString(t *testing.T) { + type args struct { + s string + } + tests := []struct { + name string + args args + want Status + }{ + { + "empty", + args{ + s: "", + }, + StatusUnknown, + }, + { + "invalid", + args{ + s: "invalid", + }, + StatusUnknown, + }, + { + "unknown", + args{ + s: "unknown", + }, + StatusUnknown, + }, + { + "active", + args{ + s: "Active", + }, + StatusActive, + }, + { + "inactive pending", + args{ + s: "InactivePending", + }, + StatusInactivePending, + }, + { + "inactive", + args{ + s: "Inactive", + }, + StatusInactive, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := StatusFromString(tt.args.s); got != tt.want { + t.Errorf("StatusFromString() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestStatus_Equal(t *testing.T) { + type args struct { + v Status + } + tests := []struct { + name string + s Status + args args + want bool + }{ + { + "unknown and unknown", + StatusUnknown, + args{ + v: StatusUnknown, + }, + true, + }, + { + "unknown and active", + StatusUnknown, + args{ + v: StatusActive, + }, + false, + }, + { + "unknown and inactive pending", + StatusUnknown, + args{ + v: StatusInactivePending, + }, + false, + }, + { + "unknown and inactive", + StatusUnknown, + args{ + v: StatusInactive, + }, + false, + }, + { + "active and unknown", + StatusActive, + args{ + v: StatusUnknown, + }, + false, + }, + { + "active and active", + StatusActive, + args{ + v: StatusActive, + }, + true, + }, + { + "active and inactive pending", + StatusActive, + args{ + v: StatusInactivePending, + }, + false, + }, + { + "active and inactive", + StatusActive, + args{ + v: StatusInactive, + }, + false, + }, + { + "inactive pending and unknown", + StatusInactivePending, + args{ + v: StatusUnknown, + }, + false, + }, + { + "inactive pending and active", + StatusInactivePending, + args{ + v: StatusActive, + }, + false, + }, + { + "inactive pending and inactive pending", + StatusInactivePending, + args{ + v: StatusInactivePending, + }, + true, + }, + { + "inactive pending and inactive", + StatusInactivePending, + args{ + v: StatusInactive, + }, + false, + }, + { + "inactive and unknown", + StatusInactive, + args{ + v: StatusUnknown, + }, + false, + }, + { + "inactive and active", + StatusInactive, + args{ + v: StatusActive, + }, + false, + }, + { + "inactive and inactive pending", + StatusInactive, + args{ + v: StatusInactivePending, + }, + false, + }, + { + "inactive and inactive", + StatusInactive, + args{ + v: StatusInactive, + }, + true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.s.Equal(tt.args.v); got != tt.want { + t.Errorf("Equal() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestStatus_IsValid(t *testing.T) { + tests := []struct { + name string + s Status + want bool + }{ + { + "unknown", + StatusUnknown, + false, + }, + { + "active", + StatusActive, + true, + }, + { + "inactive pending", + StatusInactive, + true, + }, + { + "inactive", + StatusInactive, + true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.s.IsValid(); got != tt.want { + t.Errorf("IsValid() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/x/deposit/genesis.go b/x/deposit/genesis.go index 17895198..82d5620c 100644 --- a/x/deposit/genesis.go +++ b/x/deposit/genesis.go @@ -14,9 +14,7 @@ func InitGenesis(ctx sdk.Context, k keeper.Keeper, state types.GenesisState) { } func ExportGenesis(ctx sdk.Context, k keeper.Keeper) types.GenesisState { - var ( - deposits = k.GetDeposits(ctx, 0, 0) + return types.NewGenesisState( + k.GetDeposits(ctx, 0, 0), ) - - return types.NewGenesisState(deposits) } diff --git a/x/deposit/simulation/decoder.go b/x/deposit/simulation/decoder.go new file mode 100644 index 00000000..a1097dd3 --- /dev/null +++ b/x/deposit/simulation/decoder.go @@ -0,0 +1,25 @@ +package simulation + +import ( + "bytes" + "fmt" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/types/kv" + + "github.com/sentinel-official/hub/x/deposit/types" +) + +func NewDecodeStore(cdc codec.Marshaler) func(kvA, kvB kv.Pair) string { + return func(kvA, kvB kv.Pair) string { + if bytes.Equal(kvA.Key[:1], types.DepositKeyPrefix) { + var depositA, depositB types.Deposit + cdc.MustUnmarshalBinaryBare(kvA.Value, &depositA) + cdc.MustUnmarshalBinaryBare(kvB.Value, &depositB) + + return fmt.Sprintf("%v\n%v", depositA, depositB) + } + + panic(fmt.Sprintf("invalid key prefix %X", kvA.Key[:1])) + } +} diff --git a/x/deposit/simulation/genesis.go b/x/deposit/simulation/genesis.go new file mode 100644 index 00000000..85d62089 --- /dev/null +++ b/x/deposit/simulation/genesis.go @@ -0,0 +1,13 @@ +package simulation + +import ( + "github.com/cosmos/cosmos-sdk/types/module" + + "github.com/sentinel-official/hub/x/deposit/types" +) + +func RandomizedGenesisState(_ *module.SimulationState) types.GenesisState { + return types.NewGenesisState( + nil, + ) +} diff --git a/x/deposit/types/deposit.go b/x/deposit/types/deposit.go index d7368d97..ff95bf85 100644 --- a/x/deposit/types/deposit.go +++ b/x/deposit/types/deposit.go @@ -4,14 +4,15 @@ import ( "fmt" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/errors" ) -func (d *Deposit) GetAddress() sdk.AccAddress { - if d.Address == "" { +func (m *Deposit) GetAddress() sdk.AccAddress { + if m.Address == "" { return nil } - address, err := sdk.AccAddressFromBech32(d.Address) + address, err := sdk.AccAddressFromBech32(m.Address) if err != nil { panic(err) } @@ -19,12 +20,21 @@ func (d *Deposit) GetAddress() sdk.AccAddress { return address } -func (d *Deposit) Validate() error { - if _, err := sdk.AccAddressFromBech32(d.Address); err != nil { - return err +func (m *Deposit) Validate() error { + if m.Address == "" { + return fmt.Errorf("address cannot be empty") } - if d.Coins == nil || !d.Coins.IsValid() { - return fmt.Errorf("invalid coins; expected non-nil and valid value") + if _, err := sdk.AccAddressFromBech32(m.Address); err != nil { + return errors.Wrapf(err, "invalid address %s", m.Address) + } + if m.Coins == nil { + return fmt.Errorf("coins cannot be nil") + } + if m.Coins.Len() == 0 { + return fmt.Errorf("coins cannot be empty") + } + if !m.Coins.IsValid() { + return fmt.Errorf("coins must be valid") } return nil diff --git a/x/deposit/types/deposit_test.go b/x/deposit/types/deposit_test.go new file mode 100644 index 00000000..dcd169e7 --- /dev/null +++ b/x/deposit/types/deposit_test.go @@ -0,0 +1,174 @@ +package types + +import ( + "reflect" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + + _ "github.com/sentinel-official/hub/types" +) + +func TestDeposit_GetAddress(t *testing.T) { + type fields struct { + Address string + Coins sdk.Coins + } + tests := []struct { + name string + fields fields + want sdk.AccAddress + }{ + { + "empty", + fields{ + Address: "", + }, + nil, + }, + { + "20 bytes", + fields{ + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + }, + sdk.AccAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + d := &Deposit{ + Address: tt.fields.Address, + Coins: tt.fields.Coins, + } + if got := d.GetAddress(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("GetAddress() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestDeposit_Validate(t *testing.T) { + type fields struct { + Address string + Coins sdk.Coins + } + tests := []struct { + name string + fields fields + wantErr bool + }{ + { + "empty address", + fields{ + Address: "", + Coins: nil, + }, + true, + }, + { + "invalid address", + fields{ + Address: "invalid", + Coins: nil, + }, + true, + }, + { + "invalid prefix address", + fields{ + Address: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Coins: nil, + }, + true, + }, + { + "10 bytes address", + fields{ + Address: "sent1qypqxpq9qcrsszgslawd5s", + Coins: nil, + }, + true, + }, + { + "20 bytes address", + fields{ + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + }, + true, + }, + { + "30 bytes address", + fields{ + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfqyy3zxfp9ycnjs2fszvfck8", + }, + true, + }, + { + "nil coins", + fields{ + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Coins: nil, + }, + true, + }, + { + "empty coins", + fields{ + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Coins: sdk.Coins{}, + }, + true, + }, + { + "empty denom coins", + fields{ + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Coins: sdk.Coins{sdk.Coin{Denom: ""}}, + }, + true, + }, + { + "invalid denom coins", + fields{ + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Coins: sdk.Coins{sdk.Coin{Denom: "o"}}, + }, + true, + }, + { + "negative amount coins", + fields{ + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Coins: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(-1000)}}, + }, + true, + }, + { + "zero amount coins", + fields{ + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Coins: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(0)}}, + }, + true, + }, + { + "positive amount coins", + fields{ + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Coins: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}}, + }, + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + d := &Deposit{ + Address: tt.fields.Address, + Coins: tt.fields.Coins, + } + if err := d.Validate(); (err != nil) != tt.wantErr { + t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/x/deposit/types/errors.go b/x/deposit/types/errors.go index 160bfb79..b8b40921 100644 --- a/x/deposit/types/errors.go +++ b/x/deposit/types/errors.go @@ -5,11 +5,10 @@ import ( ) var ( - ErrorMarshal = errors.Register(ModuleName, 101, "error occurred while marshalling") - ErrorUnmarshal = errors.Register(ModuleName, 102, "error occurred while unmarshalling") - ErrorUnknownMsgType = errors.Register(ModuleName, 103, "unknown message type") - ErrorUnknownQueryType = errors.Register(ModuleName, 104, "unknown query type") - ErrorInvalidField = errors.Register(ModuleName, 105, "invalid field") - ErrorInsufficientDepositFunds = errors.Register(ModuleName, 106, "insufficient deposit funds") - ErrorDepositDoesNotExist = errors.Register(ModuleName, 107, "deposit does not exist") + ErrorInvalidField = errors.Register(ModuleName, 101, "invalid field") +) + +var ( + ErrorInsufficientDepositFunds = errors.Register(ModuleName, 201, "insufficient deposit funds") + ErrorDepositDoesNotExist = errors.Register(ModuleName, 202, "deposit does not exist") ) diff --git a/x/deposit/types/genesis.go b/x/deposit/types/genesis.go index a2ad07ed..28aac516 100644 --- a/x/deposit/types/genesis.go +++ b/x/deposit/types/genesis.go @@ -17,20 +17,20 @@ func DefaultGenesisState() GenesisState { } func ValidateGenesis(state GenesisState) error { - for _, deposit := range state { - if err := deposit.Validate(); err != nil { - return err - } - } - deposits := make(map[string]bool) for _, deposit := range state { if deposits[deposit.Address] { - return fmt.Errorf("found duplicate deposit address %s", deposit.Address) + return fmt.Errorf("found duplicate deposit for address %s", deposit.Address) } deposits[deposit.Address] = true } + for _, deposit := range state { + if err := deposit.Validate(); err != nil { + return err + } + } + return nil } diff --git a/x/deposit/types/genesis_test.go b/x/deposit/types/genesis_test.go new file mode 100644 index 00000000..9d39e2eb --- /dev/null +++ b/x/deposit/types/genesis_test.go @@ -0,0 +1,45 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestDefaultGenesisState(t *testing.T) { + var ( + state GenesisState + ) + + state = DefaultGenesisState() + require.Nil(t, state) +} + +func TestNewGenesisState(t *testing.T) { + var ( + deposits Deposits + state GenesisState + ) + + state = NewGenesisState(nil) + require.Nil(t, state) + require.Len(t, state, 0) + + state = NewGenesisState(deposits) + require.Equal(t, GenesisState(nil), state) + require.Len(t, state, 0) + + deposits = append(deposits, + Deposit{}, + ) + state = NewGenesisState(deposits) + require.Equal(t, GenesisState(deposits), state) + require.Len(t, state, 1) + + deposits = append(deposits, + Deposit{}, + ) + state = NewGenesisState(deposits) + require.Equal(t, GenesisState(deposits), state) + require.Len(t, state, 2) +} diff --git a/x/node/expected/keeper.go b/x/node/expected/keeper.go index 84f6cf06..d571e619 100644 --- a/x/node/expected/keeper.go +++ b/x/node/expected/keeper.go @@ -5,20 +5,22 @@ import ( authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" hubtypes "github.com/sentinel-official/hub/types" - providertypes "github.com/sentinel-official/hub/x/provider/types" ) type AccountKeeper interface { GetAccount(ctx sdk.Context, address sdk.AccAddress) authtypes.AccountI } +type BankKeeper interface { + SpendableCoins(ctx sdk.Context, address sdk.AccAddress) sdk.Coins +} + type DistributionKeeper interface { FundCommunityPool(ctx sdk.Context, amount sdk.Coins, sender sdk.AccAddress) error } type ProviderKeeper interface { HasProvider(ctx sdk.Context, address hubtypes.ProvAddress) bool - GetProviders(ctx sdk.Context, skip, limit int64) providertypes.Providers } type PlanKeeper interface { diff --git a/x/node/genesis.go b/x/node/genesis.go index fd34a6b3..5b7573d4 100644 --- a/x/node/genesis.go +++ b/x/node/genesis.go @@ -11,25 +11,37 @@ import ( func InitGenesis(ctx sdk.Context, k keeper.Keeper, state *types.GenesisState) { k.SetParams(ctx, state.Params) + var ( + inactiveDuration = k.InactiveDuration(ctx) + ) + for _, node := range state.Nodes { + var ( + address = node.GetAddress() + provider = node.GetProvider() + ) + k.SetNode(ctx, node) if node.Status.Equal(hubtypes.StatusActive) { - k.SetActiveNode(ctx, node.GetAddress()) + k.SetActiveNode(ctx, address) if node.Provider != "" { - k.SetActiveNodeForProvider(ctx, node.GetProvider(), node.GetAddress()) + k.SetActiveNodeForProvider(ctx, provider, address) } - k.SetInactiveNodeAt(ctx, node.StatusAt, node.GetAddress()) + k.SetInactiveNodeAt(ctx, node.StatusAt.Add(inactiveDuration), address) } else { - k.SetInactiveNode(ctx, node.GetAddress()) + k.SetInactiveNode(ctx, address) if node.Provider != "" { - k.SetInactiveNodeForProvider(ctx, node.GetProvider(), node.GetAddress()) + k.SetInactiveNodeForProvider(ctx, provider, address) } } } } func ExportGenesis(ctx sdk.Context, k keeper.Keeper) *types.GenesisState { - return types.NewGenesisState(k.GetNodes(ctx, 0, 0), k.GetParams(ctx)) + return types.NewGenesisState( + k.GetNodes(ctx, 0, 0), + k.GetParams(ctx), + ) } diff --git a/x/node/simulation/decoder.go b/x/node/simulation/decoder.go new file mode 100644 index 00000000..8244ebea --- /dev/null +++ b/x/node/simulation/decoder.go @@ -0,0 +1,57 @@ +package simulation + +import ( + "bytes" + "fmt" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/types/kv" + protobuftypes "github.com/gogo/protobuf/types" + + "github.com/sentinel-official/hub/x/node/types" +) + +func NewStoreDecoder(cdc codec.Marshaler) func(kvA, kvB kv.Pair) string { + return func(kvA, kvB kv.Pair) string { + switch { + case bytes.Equal(kvA.Key[:1], types.NodeKeyPrefix): + var nodeA, nodeB types.Node + cdc.MustUnmarshalBinaryBare(kvA.Value, &nodeA) + cdc.MustUnmarshalBinaryBare(kvB.Value, &nodeB) + + return fmt.Sprintf("%v\n%v", nodeA, nodeB) + case bytes.Equal(kvA.Key[:1], types.ActiveNodeKeyPrefix): + var activeNodeA, activeNodeB protobuftypes.BoolValue + cdc.MustUnmarshalBinaryBare(kvA.Value, &activeNodeA) + cdc.MustUnmarshalBinaryBare(kvB.Value, &activeNodeB) + + return fmt.Sprintf("%v\n%v", &activeNodeA, &activeNodeB) + case bytes.Equal(kvA.Key[:1], types.InactiveNodeKeyPrefix): + var inactiveNodeA, inactiveNodeB protobuftypes.BoolValue + cdc.MustUnmarshalBinaryBare(kvA.Value, &inactiveNodeA) + cdc.MustUnmarshalBinaryBare(kvB.Value, &inactiveNodeB) + + return fmt.Sprintf("%v\n%v", &inactiveNodeA, &inactiveNodeB) + case bytes.Equal(kvA.Key[:1], types.ActiveNodeForProviderKeyPrefix): + var activeNodeForProviderA, activeNodeForProviderB protobuftypes.BoolValue + cdc.MustUnmarshalBinaryBare(kvA.Value, &activeNodeForProviderA) + cdc.MustUnmarshalBinaryBare(kvB.Value, &activeNodeForProviderB) + + return fmt.Sprintf("%v\n%v", &activeNodeForProviderA, &activeNodeForProviderB) + case bytes.Equal(kvA.Key[:1], types.InactiveNodeForProviderKeyPrefix): + var inactiveNodeForProviderA, inactiveNodeForProviderB protobuftypes.BoolValue + cdc.MustUnmarshalBinaryBare(kvA.Value, &inactiveNodeForProviderA) + cdc.MustUnmarshalBinaryBare(kvB.Value, &inactiveNodeForProviderB) + + return fmt.Sprintf("%v\n%v", &inactiveNodeForProviderA, &inactiveNodeForProviderB) + case bytes.Equal(kvA.Key[:1], types.InactiveNodeAtKeyPrefix): + var inactiveNodeAtA, inactiveNodeAtB protobuftypes.BoolValue + cdc.MustUnmarshalBinaryBare(kvA.Value, &inactiveNodeAtA) + cdc.MustUnmarshalBinaryBare(kvB.Value, &inactiveNodeAtB) + + return fmt.Sprintf("%v\n%v", &inactiveNodeAtA, &inactiveNodeAtB) + } + + panic(fmt.Sprintf("invalid key prefix %X", kvA.Key[:1])) + } +} diff --git a/x/node/simulation/genesis.go b/x/node/simulation/genesis.go new file mode 100644 index 00000000..1f8561bb --- /dev/null +++ b/x/node/simulation/genesis.go @@ -0,0 +1,45 @@ +package simulation + +import ( + "math/rand" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + + "github.com/sentinel-official/hub/x/node/types" +) + +func RandomizedGenesisState(state *module.SimulationState) *types.GenesisState { + var ( + deposit sdk.Coin + inactiveDuration time.Duration + ) + + state.AppParams.GetOrGenerate( + state.Cdc, + string(types.KeyDeposit), + &deposit, + state.Rand, + func(r *rand.Rand) { + deposit = sdk.NewInt64Coin( + sdk.DefaultBondDenom, + r.Int63n(MaxDepositAmount), + ) + }, + ) + state.AppParams.GetOrGenerate( + state.Cdc, + string(types.KeyInactiveDuration), + &inactiveDuration, + state.Rand, + func(r *rand.Rand) { + inactiveDuration = time.Duration(r.Int63n(MaxInactiveDuration)) * time.Millisecond + }, + ) + + return types.NewGenesisState( + RandomNodes(state.Rand, state.Accounts), + types.NewParams(deposit, inactiveDuration), + ) +} diff --git a/x/node/simulation/operations.go b/x/node/simulation/operations.go new file mode 100644 index 00000000..5d075a3a --- /dev/null +++ b/x/node/simulation/operations.go @@ -0,0 +1,290 @@ +package simulation + +import ( + "fmt" + "math/rand" + + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/simapp/helpers" + "github.com/cosmos/cosmos-sdk/simapp/params" + sdk "github.com/cosmos/cosmos-sdk/types" + simulationtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/cosmos/cosmos-sdk/x/simulation" + + hubtypes "github.com/sentinel-official/hub/types" + simulationhubtypes "github.com/sentinel-official/hub/types/simulation" + "github.com/sentinel-official/hub/x/node/expected" + "github.com/sentinel-official/hub/x/node/keeper" + "github.com/sentinel-official/hub/x/node/types" +) + +var ( + OperationWeightMsgRegisterRequest = "op_weight_" + types.TypeMsgRegisterRequest + OperationWeightMsgUpdateRequest = "op_weight_" + types.TypeMsgUpdateRequest + OperationWeightMsgSetStatusRequest = "op_weight_" + types.TypeMsgSetStatusRequest +) + +func WeightedOperations( + params simulationtypes.AppParams, + cdc codec.JSONMarshaler, + ak expected.AccountKeeper, + bk expected.BankKeeper, + k keeper.Keeper, +) simulation.WeightedOperations { + var ( + weightMsgRegisterRequest int + weightMsgSetStatusRequest int + weightMsgUpdateRequest int + ) + + params.GetOrGenerate( + cdc, + OperationWeightMsgRegisterRequest, + &weightMsgRegisterRequest, + nil, + func(_ *rand.Rand) { + weightMsgRegisterRequest = 100 + }, + ) + params.GetOrGenerate( + cdc, + OperationWeightMsgSetStatusRequest, + &weightMsgSetStatusRequest, + nil, + func(_ *rand.Rand) { + weightMsgSetStatusRequest = 100 + }, + ) + params.GetOrGenerate( + cdc, + OperationWeightMsgUpdateRequest, + &weightMsgUpdateRequest, + nil, + func(_ *rand.Rand) { + weightMsgUpdateRequest = 100 + }, + ) + + return simulation.WeightedOperations{ + simulation.NewWeightedOperation( + weightMsgRegisterRequest, + SimulateMsgRegisterRequest(ak, bk, k), + ), + simulation.NewWeightedOperation( + weightMsgUpdateRequest, + SimulateMsgUpdateRequest(ak, bk, k), + ), + simulation.NewWeightedOperation( + weightMsgSetStatusRequest, + SimulateMsgSetStatusRequest(ak, bk, k), + ), + } +} + +func SimulateMsgRegisterRequest(ak expected.AccountKeeper, bk expected.BankKeeper, k keeper.Keeper) simulationtypes.Operation { + return func( + r *rand.Rand, + app *baseapp.BaseApp, + ctx sdk.Context, + accounts []simulationtypes.Account, + chainID string, + ) (simulationtypes.OperationMsg, []simulationtypes.FutureOperation, error) { + var ( + rFrom, _ = simulationtypes.RandomAcc(r, accounts) + from = ak.GetAccount(ctx, rFrom.Address) + ) + + found := k.HasNode(ctx, hubtypes.NodeAddress(from.GetAddress())) + if found { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgRegisterRequest, "node already exists"), nil, nil + } + + balance := bk.SpendableCoins(ctx, from.GetAddress()) + if !balance.IsAnyNegative() { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgRegisterRequest, "balance is negative"), nil, nil + } + + fees, err := simulationtypes.RandomFees(r, ctx, balance) + if err != nil { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgRegisterRequest, err.Error()), nil, err + } + + deposit := k.Deposit(ctx) + if balance.Sub(fees).AmountOf(deposit.Denom).LT(deposit.Amount) { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgRegisterRequest, "balance is less than deposit"), nil, nil + } + + var ( + price = simulationhubtypes.RandomCoins(r, balance) + remoteURL = fmt.Sprintf( + "https://%s:8080", + simulationtypes.RandStringOfLength(r, r.Intn(MaxRemoteURLLength)), + ) + ) + + var ( + txConfig = params.MakeTestEncodingConfig().TxConfig + message = types.NewMsgRegisterRequest( + from.GetAddress(), + nil, + price, + remoteURL, + ) + ) + + txn, err := helpers.GenTx( + txConfig, + []sdk.Msg{message}, + fees, + helpers.DefaultGenTxGas, + chainID, + []uint64{from.GetAccountNumber()}, + []uint64{from.GetSequence()}, + rFrom.PrivKey, + ) + if err != nil { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgRegisterRequest, err.Error()), nil, err + } + + _, _, err = app.Deliver(txConfig.TxEncoder(), txn) + if err != nil { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgRegisterRequest, err.Error()), nil, err + } + + return simulationtypes.NewOperationMsg(message, true, ""), nil, nil + } +} + +func SimulateMsgUpdateRequest(ak expected.AccountKeeper, bk expected.BankKeeper, k keeper.Keeper) simulationtypes.Operation { + return func( + r *rand.Rand, + app *baseapp.BaseApp, + ctx sdk.Context, + accounts []simulationtypes.Account, + chainID string, + ) (simulationtypes.OperationMsg, []simulationtypes.FutureOperation, error) { + var ( + rFrom, _ = simulationtypes.RandomAcc(r, accounts) + from = ak.GetAccount(ctx, rFrom.Address) + ) + + found := k.HasNode(ctx, hubtypes.NodeAddress(from.GetAddress())) + if !found { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgUpdateRequest, "node does not exist"), nil, nil + } + + balance := bk.SpendableCoins(ctx, from.GetAddress()) + if !balance.IsAnyNegative() { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgUpdateRequest, "balance is negative"), nil, nil + } + + fees, err := simulationtypes.RandomFees(r, ctx, balance) + if err != nil { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgUpdateRequest, err.Error()), nil, err + } + + var ( + price = simulationhubtypes.RandomCoins(r, balance) + remoteURL = fmt.Sprintf( + "https://%s:8080", + simulationtypes.RandStringOfLength(r, r.Intn(MaxRemoteURLLength)), + ) + ) + + var ( + txConfig = params.MakeTestEncodingConfig().TxConfig + message = types.NewMsgUpdateRequest( + hubtypes.NodeAddress(from.GetAddress()), + nil, + price, + remoteURL, + ) + ) + + txn, err := helpers.GenTx( + txConfig, + []sdk.Msg{message}, + fees, + helpers.DefaultGenTxGas, + chainID, + []uint64{from.GetAccountNumber()}, + []uint64{from.GetSequence()}, + rFrom.PrivKey, + ) + if err != nil { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgUpdateRequest, err.Error()), nil, err + } + + _, _, err = app.Deliver(txConfig.TxEncoder(), txn) + if err != nil { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgUpdateRequest, err.Error()), nil, err + } + + return simulationtypes.NewOperationMsg(message, true, ""), nil, nil + } +} + +func SimulateMsgSetStatusRequest(ak expected.AccountKeeper, bk expected.BankKeeper, k keeper.Keeper) simulationtypes.Operation { + return func( + r *rand.Rand, + app *baseapp.BaseApp, + ctx sdk.Context, + accounts []simulationtypes.Account, + chainID string, + ) (simulationtypes.OperationMsg, []simulationtypes.FutureOperation, error) { + var ( + rFrom, _ = simulationtypes.RandomAcc(r, accounts) + from = ak.GetAccount(ctx, rFrom.Address) + ) + + found := k.HasNode(ctx, hubtypes.NodeAddress(from.GetAddress())) + if !found { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgSetStatusRequest, "node does not exist"), nil, nil + } + + balance := bk.SpendableCoins(ctx, from.GetAddress()) + if !balance.IsAnyNegative() { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgSetStatusRequest, "balance is negative"), nil, nil + } + + fees, err := simulationtypes.RandomFees(r, ctx, balance) + if err != nil { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgSetStatusRequest, err.Error()), nil, err + } + + status := hubtypes.StatusActive + if rand.Intn(2) == 0 { + status = hubtypes.StatusInactive + } + + var ( + txConfig = params.MakeTestEncodingConfig().TxConfig + message = types.NewMsgSetStatusRequest( + hubtypes.NodeAddress(from.GetAddress()), + status, + ) + ) + + txn, err := helpers.GenTx( + txConfig, + []sdk.Msg{message}, + fees, + helpers.DefaultGenTxGas, + chainID, + []uint64{from.GetAccountNumber()}, + []uint64{from.GetSequence()}, + rFrom.PrivKey, + ) + if err != nil { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgSetStatusRequest, err.Error()), nil, err + } + + _, _, err = app.Deliver(txConfig.TxEncoder(), txn) + if err != nil { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgSetStatusRequest, err.Error()), nil, err + } + + return simulationtypes.NewOperationMsg(message, true, ""), nil, nil + } +} diff --git a/x/node/simulation/params.go b/x/node/simulation/params.go new file mode 100644 index 00000000..b77c7eb1 --- /dev/null +++ b/x/node/simulation/params.go @@ -0,0 +1,43 @@ +package simulation + +import ( + "fmt" + "math/rand" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + simulationtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/cosmos/cosmos-sdk/x/simulation" + + "github.com/sentinel-official/hub/x/node/types" +) + +const ( + MaxDepositAmount = 1 << 18 + MaxInactiveDuration = 1 << 18 +) + +func ParamChanges(_ *rand.Rand) []simulationtypes.ParamChange { + return []simulationtypes.ParamChange{ + simulation.NewSimParamChange( + types.ModuleName, + string(types.KeyDeposit), + func(r *rand.Rand) string { + return sdk.NewInt64Coin( + sdk.DefaultBondDenom, + r.Int63n(MaxDepositAmount), + ).String() + }, + ), + simulation.NewSimParamChange( + types.ModuleName, + string(types.KeyInactiveDuration), + func(r *rand.Rand) string { + return fmt.Sprintf( + "%s", + time.Duration(r.Int63n(MaxInactiveDuration))*time.Millisecond, + ) + }, + ), + } +} diff --git a/x/node/simulation/rand.go b/x/node/simulation/rand.go new file mode 100644 index 00000000..7cb50db8 --- /dev/null +++ b/x/node/simulation/rand.go @@ -0,0 +1,82 @@ +package simulation + +import ( + "fmt" + "math/rand" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + simulationtypes "github.com/cosmos/cosmos-sdk/types/simulation" + + hubtypes "github.com/sentinel-official/hub/types" + simulationhubtypes "github.com/sentinel-official/hub/types/simulation" + "github.com/sentinel-official/hub/x/node/types" +) + +const ( + MaxPriceAmount = 1 << 18 + MaxRemoteURLLength = 48 +) + +func RandomNode(r *rand.Rand, items types.Nodes) types.Node { + if len(items) == 0 { + return types.Node{} + } + + return items[r.Intn(len(items))] +} + +func RandomNodes(r *rand.Rand, accounts []simulationtypes.Account) types.Nodes { + var ( + duplicates = make(map[string]bool) + items = make(types.Nodes, 0, r.Intn(len(accounts))) + ) + + for len(items) < cap(items) { + var ( + account, _ = simulationtypes.RandomAcc(r, accounts) + address = hubtypes.NodeAddress(account.Address).String() + ) + + if duplicates[address] { + continue + } + + var ( + price = simulationhubtypes.RandomCoins( + r, + sdk.NewCoins( + sdk.NewInt64Coin( + sdk.DefaultBondDenom, + MaxPriceAmount, + ), + ), + ) + remoteURL = fmt.Sprintf( + "https://%s:8080", + simulationtypes.RandStringOfLength(r, r.Intn(MaxRemoteURLLength)), + ) + status = hubtypes.StatusActive + statusAt = time.Now() + ) + + if rand.Intn(2) == 0 { + status = hubtypes.StatusInactive + } + + duplicates[address] = true + items = append( + items, + types.Node{ + Address: address, + Provider: "", + Price: price, + RemoteURL: remoteURL, + Status: status, + StatusAt: statusAt, + }, + ) + } + + return items +} diff --git a/x/node/types/errors.go b/x/node/types/errors.go index 23f45436..cce9be34 100644 --- a/x/node/types/errors.go +++ b/x/node/types/errors.go @@ -5,14 +5,17 @@ import ( ) var ( - ErrorInvalidFieldFrom = errors.Register(ModuleName, 101, "invalid value for field from; expected a valid address") - ErrorProviderDoesNotExist = errors.Register(ModuleName, 102, "provider does not exist") - ErrorDuplicateNode = errors.Register(ModuleName, 103, "duplicate node") - ErrorNodeDoesNotExist = errors.Register(ModuleName, 104, "node does not exist") - ErrorInvalidFieldProviderOrPrice = errors.Register(ModuleName, 105, "invalid value for provider or price; expected either price or provider to be empty") - ErrorInvalidFieldProvider = errors.Register(ModuleName, 106, "invalid value for field provider; expected a valid provider address") - ErrorInvalidFieldPrice = errors.Register(ModuleName, 107, "invalid value for field price; expected a valid price") - ErrorInvalidFieldRemoteURL = errors.Register(ModuleName, 108, "invalid value for field remote_url; expected length between 1 and 64") - ErrorInvalidFieldStatus = errors.Register(ModuleName, 109, "invalid value for field status; expected either Active or Inactive") - ErrorInvalidPlanCount = errors.Register(ModuleName, 110, "invalid plan count") + ErrorInvalidField = errors.Register(ModuleName, 101, "invalid field") + ErrorInvalidFrom = errors.Register(ModuleName, 102, "invalid from") + ErrorInvalidProvider = errors.Register(ModuleName, 103, "invalid provider") + ErrorInvalidPrice = errors.Register(ModuleName, 104, "invalid price") + ErrorInvalidRemoteURL = errors.Register(ModuleName, 105, "invalid remote_url") + ErrorInvalidStatus = errors.Register(ModuleName, 106, "invalid status") +) + +var ( + ErrorProviderDoesNotExist = errors.Register(ModuleName, 201, "provider does not exist") + ErrorDuplicateNode = errors.Register(ModuleName, 202, "duplicate node") + ErrorNodeDoesNotExist = errors.Register(ModuleName, 203, "node does not exist") + ErrorInvalidPlanCount = errors.Register(ModuleName, 204, "invalid plan count") ) diff --git a/x/node/types/genesis.go b/x/node/types/genesis.go index 4871c902..16a0ded1 100644 --- a/x/node/types/genesis.go +++ b/x/node/types/genesis.go @@ -20,20 +20,20 @@ func ValidateGenesis(state *GenesisState) error { return err } - for _, node := range state.Nodes { - if err := node.Validate(); err != nil { - return err - } - } - nodes := make(map[string]bool) for _, node := range state.Nodes { if nodes[node.Address] { - return fmt.Errorf("found duplicate node address %s", node.Address) + return fmt.Errorf("found duplicate node for address %s", node.Address) } nodes[node.Address] = true } + for _, node := range state.Nodes { + if err := node.Validate(); err != nil { + return err + } + } + return nil } diff --git a/x/node/types/genesis_test.go b/x/node/types/genesis_test.go new file mode 100644 index 00000000..fbbb98fc --- /dev/null +++ b/x/node/types/genesis_test.go @@ -0,0 +1,61 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestDefaultGenesisState(t *testing.T) { + var ( + state *GenesisState + ) + + state = DefaultGenesisState() + require.Equal(t, &GenesisState{ + Nodes: nil, + Params: DefaultParams(), + }, state) +} + +func TestNewGenesisState(t *testing.T) { + var ( + nodes Nodes + params Params + state *GenesisState + ) + + state = NewGenesisState(nil, params) + require.Equal(t, &GenesisState{ + Nodes: nil, + Params: params, + }, state) + require.Len(t, state.Nodes, 0) + + state = NewGenesisState(nodes, params) + require.Equal(t, &GenesisState{ + Nodes: nodes, + Params: params, + }, state) + require.Len(t, state.Nodes, 0) + + nodes = append(nodes, + Node{}, + ) + state = NewGenesisState(nodes, params) + require.Equal(t, &GenesisState{ + Nodes: nodes, + Params: params, + }, state) + require.Len(t, state.Nodes, 1) + + nodes = append(nodes, + Node{}, + ) + state = NewGenesisState(nodes, params) + require.Equal(t, &GenesisState{ + Nodes: nodes, + Params: params, + }, state) + require.Len(t, state.Nodes, 2) +} diff --git a/x/node/types/keys.go b/x/node/types/keys.go index d7e9485a..00ce3194 100644 --- a/x/node/types/keys.go +++ b/x/node/types/keys.go @@ -19,6 +19,12 @@ var ( StoreKey = ModuleName ) +var ( + TypeMsgRegisterRequest = ModuleName + ":register" + TypeMsgUpdateRequest = ModuleName + ":update" + TypeMsgSetStatusRequest = ModuleName + ":set_status" +) + var ( EventModuleName = EventModule{Name: ModuleName} ) diff --git a/x/node/types/msg.go b/x/node/types/msg.go index 9b8b7dee..59e790a0 100644 --- a/x/node/types/msg.go +++ b/x/node/types/msg.go @@ -1,9 +1,10 @@ package types import ( - "fmt" + "net/url" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/errors" hubtypes "github.com/sentinel-official/hub/types" ) @@ -28,35 +29,51 @@ func (m *MsgRegisterRequest) Route() string { } func (m *MsgRegisterRequest) Type() string { - return fmt.Sprintf("%s:register", ModuleName) + return TypeMsgRegisterRequest } func (m *MsgRegisterRequest) ValidateBasic() error { + if m.From == "" { + return errors.Wrap(ErrorInvalidFrom, "from cannot be empty") + } if _, err := sdk.AccAddressFromBech32(m.From); err != nil { - return ErrorInvalidFieldFrom + return errors.Wrapf(ErrorInvalidFrom, "%s", err) } - - // Either provider or price should be empty - if (m.Provider != "" && m.Price != nil) || - (m.Provider == "" && m.Price == nil) { - return ErrorInvalidFieldProviderOrPrice + if m.Provider == "" && m.Price == nil { + return errors.Wrap(ErrorInvalidField, "both provider and price cannot be empty") + } + if m.Provider != "" && m.Price != nil { + return errors.Wrap(ErrorInvalidField, "either provider or price must be empty") } - - // Provider can be empty. If not, it should be valid if m.Provider != "" { if _, err := hubtypes.ProvAddressFromBech32(m.Provider); err != nil { - return ErrorInvalidFieldProvider + return errors.Wrapf(ErrorInvalidProvider, "%s", err) } } - - // Price can be nil. If not, it should be valid - if m.Price != nil && !m.Price.IsValid() { - return ErrorInvalidFieldPrice + if m.Price != nil { + if m.Price.Len() == 0 { + return errors.Wrap(ErrorInvalidPrice, "price cannot be empty") + } + if !m.Price.IsValid() { + return errors.Wrap(ErrorInvalidPrice, "price must be valid") + } + } + if m.RemoteURL == "" { + return errors.Wrap(ErrorInvalidRemoteURL, "remote_url cannot be empty") + } + if len(m.RemoteURL) > 64 { + return errors.Wrapf(ErrorInvalidRemoteURL, "remote_url length cannot be greater than %d", 64) } - // RemoteURL length should be between 1 and 64 - if len(m.RemoteURL) == 0 || len(m.RemoteURL) > 64 { - return ErrorInvalidFieldRemoteURL + remoteURL, err := url.ParseRequestURI(m.RemoteURL) + if err != nil { + return errors.Wrapf(ErrorInvalidRemoteURL, "%s", err) + } + if remoteURL.Scheme != "https" { + return errors.Wrap(ErrorInvalidRemoteURL, "remote_url scheme must be https") + } + if remoteURL.Port() == "" { + return errors.Wrap(ErrorInvalidRemoteURL, "remote_url port cannot be empty") } return nil @@ -89,33 +106,47 @@ func (m *MsgUpdateRequest) Route() string { } func (m *MsgUpdateRequest) Type() string { - return fmt.Sprintf("%s:update", ModuleName) + return TypeMsgUpdateRequest } func (m *MsgUpdateRequest) ValidateBasic() error { + if m.From == "" { + return errors.Wrap(ErrorInvalidFrom, "from cannot be empty") + } if _, err := hubtypes.NodeAddressFromBech32(m.From); err != nil { - return ErrorInvalidFieldFrom + return errors.Wrapf(ErrorInvalidFrom, "%s", err) } - if m.Provider != "" && m.Price != nil { - return ErrorInvalidFieldProviderOrPrice + return errors.Wrap(ErrorInvalidField, "either provider or price must be empty") } - - // Provider can be empty. If not, it should be valid if m.Provider != "" { if _, err := hubtypes.ProvAddressFromBech32(m.Provider); err != nil { - return ErrorInvalidFieldProvider + return errors.Wrapf(ErrorInvalidProvider, "%s", err) } } - - // Price can be nil. If not, it should be valid - if m.Price != nil && !m.Price.IsValid() { - return ErrorInvalidFieldPrice + if m.Price != nil { + if m.Price.Len() == 0 { + return errors.Wrap(ErrorInvalidPrice, "price cannot be empty") + } + if !m.Price.IsValid() { + return errors.Wrap(ErrorInvalidPrice, "price must be valid") + } } + if m.RemoteURL != "" { + if len(m.RemoteURL) > 64 { + return errors.Wrapf(ErrorInvalidRemoteURL, "remote_url length cannot be greater than %d", 64) + } - // RemoteURL length should be between 0 and 64 - if len(m.RemoteURL) > 64 { - return ErrorInvalidFieldRemoteURL + remoteURL, err := url.ParseRequestURI(m.RemoteURL) + if err != nil { + return errors.Wrapf(ErrorInvalidRemoteURL, "%s", err) + } + if remoteURL.Scheme != "https" { + return errors.Wrap(ErrorInvalidRemoteURL, "remote_url scheme must be https") + } + if remoteURL.Port() == "" { + return errors.Wrap(ErrorInvalidRemoteURL, "remote_url port cannot be empty") + } } return nil @@ -146,17 +177,18 @@ func (m *MsgSetStatusRequest) Route() string { } func (m *MsgSetStatusRequest) Type() string { - return fmt.Sprintf("%s:set_status", ModuleName) + return TypeMsgSetStatusRequest } func (m *MsgSetStatusRequest) ValidateBasic() error { + if m.From == "" { + return errors.Wrap(ErrorInvalidFrom, "from cannot be empty") + } if _, err := hubtypes.NodeAddressFromBech32(m.From); err != nil { - return ErrorInvalidFieldFrom + return errors.Wrapf(ErrorInvalidFrom, "%s", err) } - - // Status should be either Active or Inactive if !m.Status.Equal(hubtypes.StatusActive) && !m.Status.Equal(hubtypes.StatusInactive) { - return ErrorInvalidFieldStatus + return errors.Wrap(ErrorInvalidStatus, "status must be either active or inactive") } return nil diff --git a/x/node/types/msg_test.go b/x/node/types/msg_test.go index 40477146..04303a9c 100644 --- a/x/node/types/msg_test.go +++ b/x/node/types/msg_test.go @@ -1,7 +1,6 @@ package types import ( - "errors" "strings" "testing" @@ -10,117 +9,567 @@ import ( hubtypes "github.com/sentinel-official/hub/types" ) -var ( - GT64 = strings.Repeat("sentinel", 9) -) - func TestMsgRegisterRequest_ValidateBasic(t *testing.T) { - correctAddress, err := sdk.AccAddressFromBech32("sent1grdunxx5jxd0ja75wt508sn6v39p70hhw53zs8") - if err != nil { - t.Errorf("invalid address: %s", err) + type fields struct { + From string + Provider string + Price sdk.Coins + RemoteURL string } - - wrongProvider := hubtypes.ProvAddress(correctAddress.String()) - - correctCoins := sdk.NewCoins(sdk.Coin{ - Denom: "sent", - Amount: sdk.NewInt(10), - }) - - wrongCoins := sdk.Coins{sdk.Coin{Denom: "--!sent--!", Amount: sdk.NewInt(-1)}} - tests := []struct { - name string - m *MsgRegisterRequest - want error + name string + fields fields + wantErr bool }{ - {"nil from address", NewMsgRegisterRequest(nil, nil, sdk.Coins{}, ""), ErrorInvalidFieldFrom}, - {"empty from address", NewMsgRegisterRequest(sdk.AccAddress{}, nil, sdk.Coins{}, ""), ErrorInvalidFieldFrom}, - {"nil provider and price", NewMsgRegisterRequest(correctAddress, nil, nil, ""), ErrorInvalidFieldProviderOrPrice}, - {"empty provider and nil price", NewMsgRegisterRequest(correctAddress, hubtypes.ProvAddress{}, nil, ""), ErrorInvalidFieldProviderOrPrice}, - {"invalid provider", NewMsgRegisterRequest(correctAddress, wrongProvider, nil, ""), ErrorInvalidFieldProvider}, - {"wrong price", NewMsgRegisterRequest(correctAddress, hubtypes.ProvAddress{}, wrongCoins, ""), ErrorInvalidFieldPrice}, - {"empty remote_url", NewMsgRegisterRequest(correctAddress, nil, correctCoins, ""), ErrorInvalidFieldRemoteURL}, - {"remote_url GT64", NewMsgRegisterRequest(correctAddress, nil, correctCoins, GT64), ErrorInvalidFieldRemoteURL}, - {"valid", NewMsgRegisterRequest(correctAddress, nil, correctCoins, "https://sentinel.co"), nil}, + { + "empty from", + fields{ + From: "", + }, + true, + }, + { + "invalid from", + fields{ + From: "invalid", + }, + true, + }, + { + "invalid prefix from", + fields{ + From: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + }, + true, + }, + { + "10 bytes from", + fields{ + From: "sent1qypqxpq9qcrsszgslawd5s", + }, + true, + }, + { + "20 bytes from", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + }, + true, + }, + { + "30 bytes from", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfqyy3zxfp9ycnjs2fszvfck8", + }, + true, + }, + { + "empty provider and nil price", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Provider: "", + Price: nil, + }, + true, + }, + { + "non-empty provider and non-nil price", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Provider: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Price: sdk.Coins{}, + }, + true, + }, + { + "invalid prefix provider", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Provider: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + }, + true, + }, + { + "10 bytes provider", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Provider: "sentprov1qypqxpq9qcrsszgsutj8xr", + }, + true, + }, + { + "20 bytes provider", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Provider: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + }, + true, + }, + { + "30 bytes provider", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Provider: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfqyy3zxfp9ycnjs2fsh33zgx", + }, + true, + }, + { + "empty price", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Provider: "", + Price: sdk.Coins{}, + }, + true, + }, + { + "empty denom price", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Provider: "", + Price: sdk.Coins{sdk.Coin{Denom: ""}}, + }, + true, + }, + { + "invalid denom price", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Provider: "", + Price: sdk.Coins{sdk.Coin{Denom: "o"}}, + }, + true, + }, + { + "negative amount price", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Provider: "", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(-1000)}}, + }, + true, + }, + { + "zero amount price", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Provider: "", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(0)}}, + }, + true, + }, + { + "positive amount price", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Provider: "", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}}, + }, + true, + }, + { + "empty remote_url", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Provider: "", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}}, + RemoteURL: "", + }, + true, + }, + { + "length 72 remote_url", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Provider: "", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}}, + RemoteURL: strings.Repeat("r", 72), + }, + true, + }, + { + "invalid remote_url", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Provider: "", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}}, + RemoteURL: "invalid", + }, + true, + }, + { + "invalid remote_url scheme", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Provider: "", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}}, + RemoteURL: "tcp://remote.url:80", + }, + true, + }, + { + "empty remote_url port", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Provider: "", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}}, + RemoteURL: "https://remote.url", + }, + true, + }, + { + "non-empty remote_url port", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Provider: "", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}}, + RemoteURL: "https://remote.url:443", + }, + false, + }, } - for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := tt.m.ValidateBasic(); !errors.Is(err, tt.want) { - t.Errorf("ValidateBasic() = %s, want %s", err, tt.want) + m := &MsgRegisterRequest{ + From: tt.fields.From, + Provider: tt.fields.Provider, + Price: tt.fields.Price, + RemoteURL: tt.fields.RemoteURL, + } + if err := m.ValidateBasic(); (err != nil) != tt.wantErr { + t.Errorf("ValidateBasic() error = %v, wantErr %v", err, tt.wantErr) } }) } } func TestMsgUpdateRequest_ValidateBasic(t *testing.T) { - nodeAddress, err := hubtypes.NodeAddressFromBech32("sentnode1grdunxx5jxd0ja75wt508sn6v39p70hhczsm43") - if err != nil { - t.Errorf("invalid provider address: %s", err) + type fields struct { + From string + Provider string + Price sdk.Coins + RemoteURL string } - - provAddress, err := hubtypes.ProvAddressFromBech32("sentprov1grdunxx5jxd0ja75wt508sn6v39p70hhxrdetl") - if err != nil { - t.Errorf("invalid provider address: %s", err) - } - - wrongProvAddress := hubtypes.ProvAddress("sentwrongprov1grdunxx5jxd0ja75wt508sn6v39p70hhxrdetl") - - correctCoins := sdk.NewCoins(sdk.Coin{ - Denom: "sent", - Amount: sdk.NewInt(10), - }) - - wrongCoins := sdk.Coins{sdk.Coin{Denom: "--!sent--!", Amount: sdk.NewInt(-1)}} - tests := []struct { - name string - m *MsgUpdateRequest - want error + name string + fields fields + wantErr bool }{ - {"nil from address", NewMsgUpdateRequest(nil, nil, sdk.Coins{}, ""), ErrorInvalidFieldFrom}, - {"empty from address", NewMsgUpdateRequest(hubtypes.NodeAddress{}, nil, sdk.Coins{}, ""), ErrorInvalidFieldFrom}, - {"non-empty provider and price", NewMsgUpdateRequest(nodeAddress, provAddress, correctCoins, ""), ErrorInvalidFieldProviderOrPrice}, - {"wrong provider", NewMsgUpdateRequest(nodeAddress, wrongProvAddress, nil, ""), ErrorInvalidFieldProvider}, - {"wrong price", NewMsgUpdateRequest(nodeAddress, hubtypes.ProvAddress{}, wrongCoins, ""), ErrorInvalidFieldPrice}, - {"remote_url GT64", NewMsgUpdateRequest(nodeAddress, hubtypes.ProvAddress{}, correctCoins, GT64), ErrorInvalidFieldRemoteURL}, - {"valid", NewMsgUpdateRequest(nodeAddress, hubtypes.ProvAddress{}, correctCoins, "https://sentinel.co"), nil}, + { + "empty from", + fields{ + From: "", + }, + true, + }, + { + "invalid from", + fields{ + From: "invalid", + }, + true, + }, + { + "invalid prefix from", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + }, + true, + }, + { + "10 bytes from", + fields{ + From: "sentnode1qypqxpq9qcrsszgse4wwrm", + }, + true, + }, + { + "20 bytes from", + fields{ + From: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + }, + false, + }, + { + "30 bytes from", + fields{ + From: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqyy3zxfp9ycnjs2fsxqglcv", + }, + true, + }, + { + "empty provider and nil price", + fields{ + From: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Provider: "", + Price: nil, + }, + false, + }, + { + "non-empty provider and non-nil price", + fields{ + From: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Provider: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Price: sdk.Coins{}, + }, + true, + }, + { + "invalid prefix provider", + fields{ + From: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Provider: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + }, + true, + }, + { + "10 bytes provider", + fields{ + From: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Provider: "sentprov1qypqxpq9qcrsszgsutj8xr", + }, + true, + }, + { + "20 bytes provider", + fields{ + From: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Provider: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + }, + false, + }, + { + "30 bytes provider", + fields{ + From: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Provider: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfqyy3zxfp9ycnjs2fsh33zgx", + }, + true, + }, + { + "empty price", + fields{ + From: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Provider: "", + Price: sdk.Coins{}, + }, + true, + }, + { + "empty denom price", + fields{ + From: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Provider: "", + Price: sdk.Coins{sdk.Coin{Denom: ""}}, + }, + true, + }, + { + "invalid denom price", + fields{ + From: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Provider: "", + Price: sdk.Coins{sdk.Coin{Denom: "o"}}, + }, + true, + }, + { + "negative amount price", + fields{ + From: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Provider: "", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(-1000)}}, + }, + true, + }, + { + "zero amount price", + fields{ + From: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Provider: "", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(0)}}, + }, + true, + }, + { + "positive amount price", + fields{ + From: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Provider: "", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}}, + }, + false, + }, + { + "empty remote_url", + fields{ + From: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Provider: "", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}}, + RemoteURL: "", + }, + false, + }, + { + "length 72 remote_url", + fields{ + From: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Provider: "", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}}, + RemoteURL: strings.Repeat("r", 72), + }, + true, + }, + { + "invalid remote_url", + fields{ + From: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Provider: "", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}}, + RemoteURL: "invalid", + }, + true, + }, + { + "invalid remote_url scheme", + fields{ + From: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Provider: "", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}}, + RemoteURL: "tcp://remote.url:80", + }, + true, + }, + { + "empty remote_url port", + fields{ + From: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Provider: "", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}}, + RemoteURL: "https://remote.url", + }, + true, + }, + { + "non-empty remote_url port", + fields{ + From: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Provider: "", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}}, + RemoteURL: "https://remote.url:443", + }, + false, + }, } - for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := tt.m.ValidateBasic(); !errors.Is(err, tt.want) { - t.Errorf("ValidateBasic() = %s, want %s", err, tt.want) + m := &MsgUpdateRequest{ + From: tt.fields.From, + Provider: tt.fields.Provider, + Price: tt.fields.Price, + RemoteURL: tt.fields.RemoteURL, + } + if err := m.ValidateBasic(); (err != nil) != tt.wantErr { + t.Errorf("ValidateBasic() error = %v, wantErr %v", err, tt.wantErr) } }) } } func TestMsgSetStatusRequest_ValidateBasic(t *testing.T) { - - nodeAddress, err := hubtypes.NodeAddressFromBech32("sentnode1grdunxx5jxd0ja75wt508sn6v39p70hhczsm43") - if err != nil { - t.Errorf("invalid provider address: %s", err) + type fields struct { + From string + Status hubtypes.Status } - - status := hubtypes.StatusInactivePending - tests := []struct { - name string - m *MsgSetStatusRequest - want error + name string + fields fields + wantErr bool }{ - {"nil from address", NewMsgSetStatusRequest(nil, status), ErrorInvalidFieldFrom}, - {"empty from address", NewMsgSetStatusRequest(hubtypes.NodeAddress{}, status), ErrorInvalidFieldFrom}, - {"wrong status", NewMsgSetStatusRequest(nodeAddress, status), ErrorInvalidFieldStatus}, - {"valid", NewMsgSetStatusRequest(nodeAddress, hubtypes.Active), nil}, + { + "empty from", + fields{ + From: "", + }, + true, + }, + { + "invalid from", + fields{ + From: "invalid", + }, + true, + }, + { + "invalid prefix from", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + }, + true, + }, + { + "10 bytes from", + fields{ + From: "sentnode1qypqxpq9qcrsszgse4wwrm", + }, + true, + }, + { + "20 bytes from", + fields{ + From: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + }, + true, + }, + { + "30 bytes from", + fields{ + From: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqyy3zxfp9ycnjs2fsxqglcv", + }, + true, + }, + { + "unknown status", + fields{ + From: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Status: hubtypes.StatusUnknown, + }, + true, + }, + { + "active status", + fields{ + From: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Status: hubtypes.StatusActive, + }, + false, + }, + { + "inactive pending status", + fields{ + From: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Status: hubtypes.StatusInactivePending, + }, + true, + }, + { + "inactive status", + fields{ + From: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Status: hubtypes.StatusInactive, + }, + false, + }, } - for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := tt.m.ValidateBasic(); !errors.Is(err, tt.want) { - t.Errorf("ValidateBasic() = %s, want %s", err, tt.want) + m := &MsgSetStatusRequest{ + From: tt.fields.From, + Status: tt.fields.Status, + } + if err := m.ValidateBasic(); (err != nil) != tt.wantErr { + t.Errorf("ValidateBasic() error = %v, wantErr %v", err, tt.wantErr) } }) } diff --git a/x/node/types/node.go b/x/node/types/node.go index 59525caf..8a202a52 100644 --- a/x/node/types/node.go +++ b/x/node/types/node.go @@ -2,18 +2,20 @@ package types import ( "fmt" + "net/url" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/errors" hubtypes "github.com/sentinel-official/hub/types" ) -func (n *Node) GetAddress() hubtypes.NodeAddress { - if n.Address == "" { +func (m *Node) GetAddress() hubtypes.NodeAddress { + if m.Address == "" { return nil } - address, err := hubtypes.NodeAddressFromBech32(n.Address) + address, err := hubtypes.NodeAddressFromBech32(m.Address) if err != nil { panic(err) } @@ -21,12 +23,12 @@ func (n *Node) GetAddress() hubtypes.NodeAddress { return address } -func (n *Node) GetProvider() hubtypes.ProvAddress { - if n.Provider == "" { +func (m *Node) GetProvider() hubtypes.ProvAddress { + if m.Provider == "" { return nil } - address, err := hubtypes.ProvAddressFromBech32(n.Provider) + address, err := hubtypes.ProvAddressFromBech32(m.Provider) if err != nil { panic(err) } @@ -34,35 +36,62 @@ func (n *Node) GetProvider() hubtypes.ProvAddress { return address } -func (n *Node) Validate() error { - if _, err := hubtypes.NodeAddressFromBech32(n.Address); err != nil { - return err +func (m *Node) Validate() error { + if m.Address == "" { + return fmt.Errorf("address cannot be empty") } - if (n.Provider != "" && n.Price != nil) || - (n.Provider == "" && n.Price == nil) { - return fmt.Errorf("invalid provider and price combination; expected one of them empty") + if _, err := hubtypes.NodeAddressFromBech32(m.Address); err != nil { + return errors.Wrapf(err, "invalid address %s", m.Address) } - if _, err := hubtypes.ProvAddressFromBech32(n.Provider); err != nil { - return err + if m.Provider == "" && m.Price == nil { + return fmt.Errorf("both provider and price cannot be empty") } - if n.Price != nil && !n.Price.IsValid() { - return fmt.Errorf("invalid price; expected non-nil and valid value") + if m.Provider != "" && m.Price != nil { + return fmt.Errorf("either provider or price must be empty") } - if len(n.RemoteURL) == 0 || len(n.RemoteURL) > 64 { - return fmt.Errorf("invalid remote url length; expected length is between 1 and 64") + if m.Provider != "" { + if _, err := hubtypes.ProvAddressFromBech32(m.Provider); err != nil { + return errors.Wrapf(err, "invalid provider %s", m.Provider) + } + } + if m.Price != nil { + if m.Price.Len() == 0 { + return fmt.Errorf("price cannot be empty") + } + if !m.Price.IsValid() { + return fmt.Errorf("price must be valid") + } } - if !n.Status.Equal(hubtypes.StatusActive) && !n.Status.Equal(hubtypes.StatusInactive) { - return fmt.Errorf("invalid status; exptected active or inactive") + if m.RemoteURL == "" { + return fmt.Errorf("remote_url cannot be empty") } - if n.StatusAt.IsZero() { - return fmt.Errorf("invalid status at; expected non-zero value") + if len(m.RemoteURL) > 64 { + return fmt.Errorf("remote_url length cannot be greater than %d", 64) + } + + remoteURL, err := url.ParseRequestURI(m.RemoteURL) + if err != nil { + return errors.Wrapf(err, "invalid remote_url %s", m.RemoteURL) + } + if remoteURL.Scheme != "https" { + return fmt.Errorf("remote_url scheme must be https") + } + if remoteURL.Port() == "" { + return fmt.Errorf("remote_url port cannot be empty") + } + + if !m.Status.Equal(hubtypes.StatusActive) && !m.Status.Equal(hubtypes.StatusInactive) { + return fmt.Errorf("status must be either active or inactive") + } + if m.StatusAt.IsZero() { + return fmt.Errorf("status_at cannot be zero") } return nil } -func (n *Node) PriceForDenom(s string) (sdk.Coin, bool) { - for _, coin := range n.Price { +func (m *Node) PriceForDenom(s string) (sdk.Coin, bool) { + for _, coin := range m.Price { if coin.Denom == s { return coin, true } @@ -71,8 +100,8 @@ func (n *Node) PriceForDenom(s string) (sdk.Coin, bool) { return sdk.Coin{}, false } -func (n *Node) BytesForCoin(coin sdk.Coin) (sdk.Int, error) { - price, found := n.PriceForDenom(coin.Denom) +func (m *Node) BytesForCoin(coin sdk.Coin) (sdk.Int, error) { + price, found := m.PriceForDenom(coin.Denom) if !found { return sdk.ZeroInt(), fmt.Errorf("price for denom %s does not exist", coin.Denom) } @@ -89,4 +118,6 @@ func (n *Node) BytesForCoin(coin sdk.Coin) (sdk.Int, error) { return coin.Amount.Quo(y), nil } -type Nodes []Node +type ( + Nodes []Node +) diff --git a/x/node/types/node_test.go b/x/node/types/node_test.go new file mode 100644 index 00000000..1d4942ff --- /dev/null +++ b/x/node/types/node_test.go @@ -0,0 +1,1571 @@ +package types + +import ( + "reflect" + "strings" + "testing" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + + hubtypes "github.com/sentinel-official/hub/types" +) + +func TestNode_BytesForCoin(t *testing.T) { + type fields struct { + Address string + Provider string + Price sdk.Coins + RemoteURL string + Status hubtypes.Status + StatusAt time.Time + } + type args struct { + coin sdk.Coin + } + tests := []struct { + name string + fields fields + args args + want sdk.Int + wantErr bool + }{ + { + "nil price and empty coin", + fields{ + Price: nil, + }, + args{ + coin: sdk.Coin{}, + }, + sdk.NewInt(0), + true, + }, + { + "empty price and empty coin", + fields{ + Price: sdk.Coins{}, + }, + args{ + coin: sdk.Coin{}, + }, + sdk.NewInt(0), + true, + }, + { + "1one price and empty coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 1)}, + }, + args{ + coin: sdk.Coin{}, + }, + sdk.NewInt(0), + true, + }, + { + "1one price and 0one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 1)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 0), + }, + sdk.NewInt(0), + false, + }, + { + "1one price and 1one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 1)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 1), + }, + sdk.NewInt(1000000000), + false, + }, + { + "1one price and 2one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 1)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 2), + }, + sdk.NewInt(2000000000), + false, + }, + { + "1one price and 3one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 1)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 3), + }, + sdk.NewInt(3000000000), + false, + }, + { + "1one price and 4one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 1)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 4), + }, + sdk.NewInt(4000000000), + false, + }, + { + "1one price and 5one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 1)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 5), + }, + sdk.NewInt(5000000000), + false, + }, + { + "1one price and 6one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 1)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 6), + }, + sdk.NewInt(6000000000), + false, + }, + { + "1one price and 7one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 1)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 7), + }, + sdk.NewInt(7000000000), + false, + }, + { + "1one price and 8one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 1)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 8), + }, + sdk.NewInt(8000000000), + false, + }, + { + "1one price and 9one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 1)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 9), + }, + sdk.NewInt(9000000000), + false, + }, + { + "2one price and 0one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 2)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 0), + }, + sdk.NewInt(0), + false, + }, + { + "2one price and 1one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 2)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 1), + }, + sdk.NewInt(500000000), + false, + }, + { + "2one price and 2one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 2)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 2), + }, + sdk.NewInt(1000000000), + false, + }, + { + "2one price and 3one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 2)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 3), + }, + sdk.NewInt(1500000000), + false, + }, + { + "2one price and 4one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 2)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 4), + }, + sdk.NewInt(2000000000), + false, + }, + { + "2one price and 5one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 2)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 5), + }, + sdk.NewInt(2500000000), + false, + }, + { + "2one price and 6one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 2)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 6), + }, + sdk.NewInt(3000000000), + false, + }, + { + "2one price and 7one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 2)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 7), + }, + sdk.NewInt(3500000000), + false, + }, + { + "2one price and 8one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 2)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 8), + }, + sdk.NewInt(4000000000), + false, + }, + { + "2one price and 9one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 2)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 9), + }, + sdk.NewInt(4500000000), + false, + }, + { + "3one price and 0one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 3)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 0), + }, + sdk.NewInt(0), + false, + }, + { + "3one price and 1one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 3)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 1), + }, + sdk.NewInt(333333333), + false, + }, + { + "3one price and 2one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 3)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 2), + }, + sdk.NewInt(666666666), + false, + }, + { + "3one price and 3one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 3)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 3), + }, + sdk.NewInt(999999999), + false, + }, + { + "3one price and 4one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 3)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 4), + }, + sdk.NewInt(1333333332), + false, + }, + { + "3one price and 5one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 3)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 5), + }, + sdk.NewInt(1666666665), + false, + }, + { + "3one price and 6one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 3)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 6), + }, + sdk.NewInt(1999999998), + false, + }, + { + "3one price and 7one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 3)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 7), + }, + sdk.NewInt(2333333331), + false, + }, + { + "3one price and 8one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 3)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 8), + }, + sdk.NewInt(2666666664), + false, + }, + { + "3one price and 9one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 3)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 9), + }, + sdk.NewInt(2999999997), + false, + }, + { + "4one price and 0one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 4)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 0), + }, + sdk.NewInt(0), + false, + }, + { + "4one price and 1one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 4)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 1), + }, + sdk.NewInt(250000000), + false, + }, + { + "4one price and 2one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 4)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 2), + }, + sdk.NewInt(500000000), + false, + }, + { + "4one price and 3one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 4)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 3), + }, + sdk.NewInt(750000000), + false, + }, + { + "4one price and 4one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 4)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 4), + }, + sdk.NewInt(1000000000), + false, + }, + { + "4one price and 5one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 4)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 5), + }, + sdk.NewInt(1250000000), + false, + }, + { + "4one price and 6one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 4)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 6), + }, + sdk.NewInt(1500000000), + false, + }, + { + "4one price and 7one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 4)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 7), + }, + sdk.NewInt(1750000000), + false, + }, + { + "4one price and 8one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 4)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 8), + }, + sdk.NewInt(2000000000), + false, + }, + { + "4one price and 9one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 4)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 9), + }, + sdk.NewInt(2250000000), + false, + }, + { + "5one price and 0one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 5)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 0), + }, + sdk.NewInt(0), + false, + }, + { + "5one price and 1one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 5)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 1), + }, + sdk.NewInt(200000000), + false, + }, + { + "5one price and 2one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 5)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 2), + }, + sdk.NewInt(400000000), + false, + }, + { + "5one price and 3one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 5)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 3), + }, + sdk.NewInt(600000000), + false, + }, + { + "5one price and 4one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 5)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 4), + }, + sdk.NewInt(800000000), + false, + }, + { + "5one price and 5one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 5)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 5), + }, + sdk.NewInt(1000000000), + false, + }, + { + "5one price and 6one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 5)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 6), + }, + sdk.NewInt(1200000000), + false, + }, + { + "5one price and 7one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 5)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 7), + }, + sdk.NewInt(1400000000), + false, + }, + { + "5one price and 8one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 5)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 8), + }, + sdk.NewInt(1600000000), + false, + }, + { + "5one price and 9one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 5)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 9), + }, + sdk.NewInt(1800000000), + false, + }, + { + "6one price and 0one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 6)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 0), + }, + sdk.NewInt(0), + false, + }, + { + "6one price and 1one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 6)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 1), + }, + sdk.NewInt(166666666), + false, + }, + { + "6one price and 2one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 6)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 2), + }, + sdk.NewInt(333333332), + false, + }, + { + "6one price and 3one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 6)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 3), + }, + sdk.NewInt(499999998), + false, + }, + { + "6one price and 4one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 6)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 4), + }, + sdk.NewInt(666666664), + false, + }, + { + "6one price and 5one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 6)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 5), + }, + sdk.NewInt(833333330), + false, + }, + { + "6one price and 6one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 6)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 6), + }, + sdk.NewInt(999999996), + false, + }, + { + "6one price and 7one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 6)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 7), + }, + sdk.NewInt(1166666662), + false, + }, + { + "6one price and 8one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 6)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 8), + }, + sdk.NewInt(1333333328), + false, + }, + { + "6one price and 9one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 6)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 9), + }, + sdk.NewInt(1499999994), + false, + }, + { + "7one price and 0one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 7)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 0), + }, + sdk.NewInt(0), + false, + }, + { + "7one price and 1one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 7)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 1), + }, + sdk.NewInt(142857142), + false, + }, + { + "7one price and 2one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 7)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 2), + }, + sdk.NewInt(285714284), + false, + }, + { + "7one price and 3one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 7)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 3), + }, + sdk.NewInt(428571426), + false, + }, + { + "7one price and 4one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 7)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 4), + }, + sdk.NewInt(571428568), + false, + }, + { + "7one price and 5one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 7)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 5), + }, + sdk.NewInt(714285710), + false, + }, + { + "7one price and 6one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 7)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 6), + }, + sdk.NewInt(857142852), + false, + }, + { + "7one price and 7one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 7)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 7), + }, + sdk.NewInt(999999994), + false, + }, + { + "7one price and 8one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 7)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 8), + }, + sdk.NewInt(1142857136), + false, + }, + { + "7one price and 9one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 7)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 9), + }, + sdk.NewInt(1285714278), + false, + }, + { + "8one price and 0one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 8)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 0), + }, + sdk.NewInt(0), + false, + }, + { + "8one price and 1one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 8)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 1), + }, + sdk.NewInt(125000000), + false, + }, + { + "8one price and 2one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 8)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 2), + }, + sdk.NewInt(250000000), + false, + }, + { + "8one price and 3one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 8)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 3), + }, + sdk.NewInt(375000000), + false, + }, + { + "8one price and 4one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 8)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 4), + }, + sdk.NewInt(500000000), + false, + }, + { + "8one price and 5one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 8)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 5), + }, + sdk.NewInt(625000000), + false, + }, + { + "8one price and 6one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 8)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 6), + }, + sdk.NewInt(750000000), + false, + }, + { + "8one price and 7one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 8)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 7), + }, + sdk.NewInt(875000000), + false, + }, + { + "8one price and 8one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 8)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 8), + }, + sdk.NewInt(1000000000), + false, + }, + { + "8one price and 9one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 8)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 9), + }, + sdk.NewInt(1125000000), + false, + }, + { + "9one price and 0one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 9)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 0), + }, + sdk.NewInt(0), + false, + }, + { + "9one price and 1one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 9)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 1), + }, + sdk.NewInt(111111111), + false, + }, + { + "9one price and 2one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 9)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 2), + }, + sdk.NewInt(222222222), + false, + }, + { + "9one price and 3one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 9)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 3), + }, + sdk.NewInt(333333333), + false, + }, + { + "9one price and 4one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 9)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 4), + }, + sdk.NewInt(444444444), + false, + }, + { + "9one price and 5one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 9)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 5), + }, + sdk.NewInt(555555555), + false, + }, + { + "9one price and 6one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 9)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 6), + }, + sdk.NewInt(666666666), + false, + }, + { + "9one price and 7one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 9)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 7), + }, + sdk.NewInt(777777777), + false, + }, + { + "9one price and 8one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 9)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 8), + }, + sdk.NewInt(888888888), + false, + }, + { + "9one price and 9one coin", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 9)}, + }, + args{ + coin: sdk.NewInt64Coin("one", 9), + }, + sdk.NewInt(999999999), + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := &Node{ + Address: tt.fields.Address, + Provider: tt.fields.Provider, + Price: tt.fields.Price, + RemoteURL: tt.fields.RemoteURL, + Status: tt.fields.Status, + StatusAt: tt.fields.StatusAt, + } + got, err := n.BytesForCoin(tt.args.coin) + if (err != nil) != tt.wantErr { + t.Errorf("BytesForCoin() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("BytesForCoin() got = %v, want %v", got, tt.want) + } + }) + } +} + +func TestNode_GetAddress(t *testing.T) { + type fields struct { + Address string + Provider string + Price sdk.Coins + RemoteURL string + Status hubtypes.Status + StatusAt time.Time + } + tests := []struct { + name string + fields fields + want hubtypes.NodeAddress + }{ + { + "empty", + fields{ + Address: "", + }, + nil, + }, + { + "20 bytes", + fields{ + Address: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + }, + hubtypes.NodeAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := &Node{ + Address: tt.fields.Address, + Provider: tt.fields.Provider, + Price: tt.fields.Price, + RemoteURL: tt.fields.RemoteURL, + Status: tt.fields.Status, + StatusAt: tt.fields.StatusAt, + } + if got := n.GetAddress(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("GetAddress() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestNode_GetProvider(t *testing.T) { + type fields struct { + Address string + Provider string + Price sdk.Coins + RemoteURL string + Status hubtypes.Status + StatusAt time.Time + } + tests := []struct { + name string + fields fields + want hubtypes.ProvAddress + }{ + { + "empty", + fields{ + Provider: "", + }, + nil, + }, + { + "20 bytes", + fields{ + Provider: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + }, + hubtypes.ProvAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := &Node{ + Address: tt.fields.Address, + Provider: tt.fields.Provider, + Price: tt.fields.Price, + RemoteURL: tt.fields.RemoteURL, + Status: tt.fields.Status, + StatusAt: tt.fields.StatusAt, + } + if got := n.GetProvider(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("GetProvider() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestNode_PriceForDenom(t *testing.T) { + type fields struct { + Address string + Provider string + Price sdk.Coins + RemoteURL string + Status hubtypes.Status + StatusAt time.Time + } + type args struct { + s string + } + tests := []struct { + name string + fields fields + args args + want sdk.Coin + want1 bool + }{ + { + "nil price and empty denom", + fields{ + Price: nil, + }, + args{ + s: "", + }, + sdk.Coin{}, + false, + }, + { + "empty price and empty denom", + fields{ + Price: sdk.Coins{}, + }, + args{ + s: "", + }, + sdk.Coin{}, + false, + }, + { + "1one price and empty denom", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 1)}, + }, + args{ + s: "", + }, + sdk.Coin{}, + false, + }, + { + "1one price and one denom", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 1)}, + }, + args{ + s: "one", + }, + sdk.NewInt64Coin("one", 1), + true, + }, + { + "1one price and two denom", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 1)}, + }, + args{ + s: "two", + }, + sdk.Coin{}, + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := &Node{ + Address: tt.fields.Address, + Provider: tt.fields.Provider, + Price: tt.fields.Price, + RemoteURL: tt.fields.RemoteURL, + Status: tt.fields.Status, + StatusAt: tt.fields.StatusAt, + } + got, got1 := n.PriceForDenom(tt.args.s) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("PriceForDenom() got = %v, want %v", got, tt.want) + } + if got1 != tt.want1 { + t.Errorf("PriceForDenom() got1 = %v, want %v", got1, tt.want1) + } + }) + } +} + +func TestNode_Validate(t *testing.T) { + type fields struct { + Address string + Provider string + Price sdk.Coins + RemoteURL string + Status hubtypes.Status + StatusAt time.Time + } + tests := []struct { + name string + fields fields + wantErr bool + }{ + { + "empty address", + fields{ + Address: "", + }, + true, + }, + { + "invalid address", + fields{ + Address: "invalid", + }, + true, + }, + { + "invalid prefix address", + fields{ + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + }, + true, + }, + { + "10 bytes address", + fields{ + Address: "sentnode1qypqxpq9qcrsszgse4wwrm", + }, + true, + }, + { + "20 bytes address", + fields{ + Address: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + }, + true, + }, + { + "30 bytes address", + fields{ + Address: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqyy3zxfp9ycnjs2fsxqglcv", + }, + true, + }, + { + "empty provider and nil price", + fields{ + Address: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Provider: "", + Price: nil, + }, + true, + }, + { + "non-empty provider and non-nil price", + fields{ + Address: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Provider: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Price: sdk.Coins{}, + }, + true, + }, + { + "invalid prefix provider", + fields{ + Address: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Provider: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + }, + true, + }, + { + "10 bytes provider", + fields{ + Address: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Provider: "sentprov1qypqxpq9qcrsszgsutj8xr", + }, + true, + }, + { + "20 bytes provider", + fields{ + Address: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Provider: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + }, + true, + }, + { + "30 bytes provider", + fields{ + Address: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Provider: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfqyy3zxfp9ycnjs2fsh33zgx", + }, + true, + }, + { + "empty price", + fields{ + Address: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Provider: "", + Price: sdk.Coins{}, + }, + true, + }, + { + "empty denom price", + fields{ + Address: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Provider: "", + Price: sdk.Coins{sdk.Coin{Denom: ""}}, + }, + true, + }, + { + "invalid denom price", + fields{ + Address: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Provider: "", + Price: sdk.Coins{sdk.Coin{Denom: "o"}}, + }, + true, + }, + { + "negative amount price", + fields{ + Address: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Provider: "", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(-1000)}}, + }, + true, + }, + { + "zero amount price", + fields{ + Address: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Provider: "", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(0)}}, + }, + true, + }, + { + "positive amount price", + fields{ + Address: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Provider: "", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}}, + }, + true, + }, + { + "empty remote_url", + fields{ + Address: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Provider: "", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}}, + RemoteURL: "", + }, + true, + }, + { + "length 72 remote_url", + fields{ + Address: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Provider: "", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}}, + RemoteURL: strings.Repeat("r", 72), + }, + true, + }, + { + "invalid remote_url", + fields{ + Address: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Provider: "", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}}, + RemoteURL: "invalid", + }, + true, + }, + { + "invalid remote_url scheme", + fields{ + Address: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Provider: "", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}}, + RemoteURL: "tcp://remote.url:80", + }, + true, + }, + { + "empty remote_url port", + fields{ + Address: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Provider: "", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}}, + RemoteURL: "https://remote.url", + }, + true, + }, + { + "non-empty remote_url port", + fields{ + Address: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Provider: "", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}}, + RemoteURL: "https://remote.url:443", + }, + true, + }, + { + "unknown status", + fields{ + Address: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Provider: "", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}}, + RemoteURL: "https://remote.url:443", + Status: hubtypes.StatusUnknown, + }, + true, + }, + { + "active status", + fields{ + Address: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Provider: "", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}}, + RemoteURL: "https://remote.url:443", + Status: hubtypes.StatusActive, + }, + true, + }, + { + "inactive pending status", + fields{ + Address: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Provider: "", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}}, + RemoteURL: "https://remote.url:443", + Status: hubtypes.StatusInactivePending, + }, + true, + }, + { + "inactive status", + fields{ + Address: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Provider: "", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}}, + RemoteURL: "https://remote.url:443", + Status: hubtypes.StatusInactive, + }, + true, + }, + { + "zero status_at", + fields{ + Address: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Provider: "", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}}, + RemoteURL: "https://remote.url:443", + Status: hubtypes.StatusInactive, + StatusAt: time.Time{}, + }, + true, + }, + { + "now status_at", + fields{ + Address: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Provider: "", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}}, + RemoteURL: "https://remote.url:443", + Status: hubtypes.StatusInactive, + StatusAt: time.Now(), + }, + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + n := &Node{ + Address: tt.fields.Address, + Provider: tt.fields.Provider, + Price: tt.fields.Price, + RemoteURL: tt.fields.RemoteURL, + Status: tt.fields.Status, + StatusAt: tt.fields.StatusAt, + } + if err := n.Validate(); (err != nil) != tt.wantErr { + t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/x/node/types/params.go b/x/node/types/params.go index 7e29c7d9..70ac344b 100644 --- a/x/node/types/params.go +++ b/x/node/types/params.go @@ -25,30 +25,39 @@ var ( _ params.ParamSet = (*Params)(nil) ) -func (p *Params) Validate() error { - if !p.Deposit.IsValid() { - return fmt.Errorf("deposit should be valid") +func (m *Params) Validate() error { + if m.Deposit.IsNegative() { + return fmt.Errorf("deposit cannot be negative") } - if p.InactiveDuration <= 0 { - return fmt.Errorf("inactive_duration should be positive") + if !m.Deposit.IsValid() { + return fmt.Errorf("invalid deposit %s", m.Deposit) + } + if m.InactiveDuration < 0 { + return fmt.Errorf("inactive_duration cannot be negative") + } + if m.InactiveDuration == 0 { + return fmt.Errorf("inactive_duration cannot be zero") } return nil } -func (p *Params) ParamSetPairs() params.ParamSetPairs { +func (m *Params) ParamSetPairs() params.ParamSetPairs { return params.ParamSetPairs{ { Key: KeyDeposit, - Value: &p.Deposit, + Value: &m.Deposit, ValidatorFn: func(v interface{}) error { value, ok := v.(sdk.Coin) if !ok { return fmt.Errorf("invalid parameter type %T", v) } + if value.IsNegative() { + return fmt.Errorf("deposit cannot be negative") + } if !value.IsValid() { - return fmt.Errorf("deposit value should be valid") + return fmt.Errorf("invalid deposit %s", value) } return nil @@ -56,15 +65,18 @@ func (p *Params) ParamSetPairs() params.ParamSetPairs { }, { Key: KeyInactiveDuration, - Value: &p.InactiveDuration, + Value: &m.InactiveDuration, ValidatorFn: func(v interface{}) error { value, ok := v.(time.Duration) if !ok { return fmt.Errorf("invalid parameter type %T", v) } - if value <= 0 { - return fmt.Errorf("inactive duration value should be positive") + if value < 0 { + return fmt.Errorf("inactive_duration cannot be negative") + } + if value == 0 { + return fmt.Errorf("inactive_duration cannot be zero") } return nil diff --git a/x/node/types/params_test.go b/x/node/types/params_test.go new file mode 100644 index 00000000..2b87ac12 --- /dev/null +++ b/x/node/types/params_test.go @@ -0,0 +1,91 @@ +package types + +import ( + "testing" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func TestParams_Validate(t *testing.T) { + type fields struct { + Deposit sdk.Coin + InactiveDuration time.Duration + } + tests := []struct { + name string + fields fields + wantErr bool + }{ + { + "empty denom deposit", + fields{ + Deposit: sdk.Coin{Denom: "", Amount: sdk.NewInt(1000)}, + }, + true, + }, + { + "invalid denom deposit", + fields{ + Deposit: sdk.Coin{Denom: "0", Amount: sdk.NewInt(1000)}, + }, + true, + }, + { + "negative amount deposit", + fields{ + Deposit: sdk.Coin{Denom: "one", Amount: sdk.NewInt(-1000)}, + }, + true, + }, + { + "zero amount deposit", + fields{ + Deposit: sdk.Coin{Denom: "one", Amount: sdk.NewInt(0)}, + }, + true, + }, + { + "positive amount deposit", + fields{ + Deposit: sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}, + }, + true, + }, + { + "negative inactive duration", + fields{ + Deposit: sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}, + InactiveDuration: -1000, + }, + true, + }, + { + "zero inactive duration", + fields{ + Deposit: sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}, + InactiveDuration: 0, + }, + true, + }, + { + "positive inactive duration", + fields{ + Deposit: sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}, + InactiveDuration: 1000, + }, + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := &Params{ + Deposit: tt.fields.Deposit, + InactiveDuration: tt.fields.InactiveDuration, + } + if err := p.Validate(); (err != nil) != tt.wantErr { + t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/x/plan/expected/keeper.go b/x/plan/expected/keeper.go index 6753e382..ab2aec35 100644 --- a/x/plan/expected/keeper.go +++ b/x/plan/expected/keeper.go @@ -6,20 +6,20 @@ import ( hubtypes "github.com/sentinel-official/hub/types" nodetypes "github.com/sentinel-official/hub/x/node/types" - providertypes "github.com/sentinel-official/hub/x/provider/types" ) type AccountKeeper interface { GetAccount(ctx sdk.Context, address sdk.AccAddress) authtypes.AccountI } +type BankKeeper interface { + SpendableCoins(ctx sdk.Context, address sdk.AccAddress) sdk.Coins +} + type ProviderKeeper interface { - GetProviders(ctx sdk.Context, skip, limit int64) providertypes.Providers HasProvider(ctx sdk.Context, address hubtypes.ProvAddress) bool } type NodeKeeper interface { GetNode(ctx sdk.Context, address hubtypes.NodeAddress) (nodetypes.Node, bool) - GetNodes(ctx sdk.Context, skip, limit int64) nodetypes.Nodes - GetNodesForProvider(ctx sdk.Context, address hubtypes.ProvAddress, skip, limit int64) nodetypes.Nodes } diff --git a/x/plan/genesis.go b/x/plan/genesis.go index 55e0585d..2d8c7510 100644 --- a/x/plan/genesis.go +++ b/x/plan/genesis.go @@ -10,14 +10,17 @@ import ( func InitGenesis(ctx sdk.Context, k keeper.Keeper, state types.GenesisState) { for _, item := range state { - k.SetPlan(ctx, item.Plan) + var ( + provider = item.Plan.GetProvider() + ) + k.SetPlan(ctx, item.Plan) if item.Plan.Status.Equal(hubtypes.StatusActive) { k.SetActivePlan(ctx, item.Plan.Id) - k.SetActivePlanForProvider(ctx, item.Plan.GetProvider(), item.Plan.Id) + k.SetActivePlanForProvider(ctx, provider, item.Plan.Id) } else { k.SetInactivePlan(ctx, item.Plan.Id) - k.SetInactivePlanForProvider(ctx, item.Plan.GetProvider(), item.Plan.Id) + k.SetInactivePlanForProvider(ctx, provider, item.Plan.Id) } for _, node := range item.Nodes { @@ -27,20 +30,33 @@ func InitGenesis(ctx sdk.Context, k keeper.Keeper, state types.GenesisState) { } k.SetNodeForPlan(ctx, item.Plan.Id, address) + k.IncreaseCountForNodeByProvider(ctx, provider, address) + } + } + + var ( + count uint64 = 0 + ) + + for _, item := range state { + if item.Plan.Id > count { + count = item.Plan.Id } } - k.SetCount(ctx, uint64(len(state))) + k.SetCount(ctx, count) } func ExportGenesis(ctx sdk.Context, k keeper.Keeper) types.GenesisState { - plans := k.GetPlans(ctx, 0, 0) + var ( + plans = k.GetPlans(ctx, 0, 0) + items = make(types.GenesisPlans, 0, len(plans)) + ) - items := make(types.GenesisPlans, 0, len(plans)) for _, plan := range plans { item := types.GenesisPlan{ Plan: plan, - Nodes: nil, + Nodes: []string{}, } nodes := k.GetNodesForPlan(ctx, plan.Id, 0, 0) diff --git a/x/plan/simulation/decoder.go b/x/plan/simulation/decoder.go new file mode 100644 index 00000000..fe9fb611 --- /dev/null +++ b/x/plan/simulation/decoder.go @@ -0,0 +1,69 @@ +package simulation + +import ( + "bytes" + "fmt" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/types/kv" + protobuftypes "github.com/gogo/protobuf/types" + + "github.com/sentinel-official/hub/x/plan/types" +) + +func NewStoreDecoder(cdc codec.Marshaler) func(kvA, kvB kv.Pair) string { + return func(kvA, kvB kv.Pair) string { + switch { + case bytes.Equal(kvA.Key[:1], types.CountKey): + var countA, countB protobuftypes.UInt64Value + cdc.MustUnmarshalBinaryBare(kvA.Value, &countA) + cdc.MustUnmarshalBinaryBare(kvB.Value, &countB) + + return fmt.Sprintf("%v\n%v", &countA, &countB) + case bytes.Equal(kvA.Key[:1], types.PlanKeyPrefix): + var planA, planB types.Plan + cdc.MustUnmarshalBinaryBare(kvA.Value, &planA) + cdc.MustUnmarshalBinaryBare(kvB.Value, &planB) + + return fmt.Sprintf("%v\n%v", &planA, &planB) + case bytes.Equal(kvA.Key[:1], types.ActivePlanKeyPrefix): + var activePlanA, activePlanB protobuftypes.BoolValue + cdc.MustUnmarshalBinaryBare(kvA.Value, &activePlanA) + cdc.MustUnmarshalBinaryBare(kvB.Value, &activePlanB) + + return fmt.Sprintf("%v\n%v", &activePlanA, &activePlanB) + case bytes.Equal(kvA.Key[:1], types.InactivePlanKeyPrefix): + var inactivePlanA, inactivePlanB protobuftypes.BoolValue + cdc.MustUnmarshalBinaryBare(kvA.Value, &inactivePlanA) + cdc.MustUnmarshalBinaryBare(kvB.Value, &inactivePlanB) + + return fmt.Sprintf("%v\n%v", &inactivePlanA, &inactivePlanB) + case bytes.Equal(kvA.Key[:1], types.ActivePlanForProviderKeyPrefix): + var activePlanForProviderA, activePlanForProviderB protobuftypes.BoolValue + cdc.MustUnmarshalBinaryBare(kvA.Value, &activePlanForProviderA) + cdc.MustUnmarshalBinaryBare(kvB.Value, &activePlanForProviderB) + + return fmt.Sprintf("%v\n%v", &activePlanForProviderA, &activePlanForProviderB) + case bytes.Equal(kvA.Key[:1], types.InactivePlanForProviderKeyPrefix): + var inactivePlanForProviderA, inactivePlanForProviderB protobuftypes.BoolValue + cdc.MustUnmarshalBinaryBare(kvA.Value, &inactivePlanForProviderA) + cdc.MustUnmarshalBinaryBare(kvB.Value, &inactivePlanForProviderB) + + return fmt.Sprintf("%v\n%v", &inactivePlanForProviderA, &inactivePlanForProviderB) + case bytes.Equal(kvA.Key[:1], types.NodeForPlanKeyPrefix): + var nodeForPlanA, nodeForPlanB protobuftypes.BoolValue + cdc.MustUnmarshalBinaryBare(kvA.Value, &nodeForPlanA) + cdc.MustUnmarshalBinaryBare(kvB.Value, &nodeForPlanB) + + return fmt.Sprintf("%v\n%v", &nodeForPlanA, &nodeForPlanB) + case bytes.Equal(kvA.Key[:1], types.CountForNodeByProviderKeyPrefix): + var countForNodeByProviderA, countForNodeByProviderB protobuftypes.UInt64Value + cdc.MustUnmarshalBinaryBare(kvA.Value, &countForNodeByProviderA) + cdc.MustUnmarshalBinaryBare(kvB.Value, &countForNodeByProviderB) + + return fmt.Sprintf("%v\n%v", &countForNodeByProviderA, &countForNodeByProviderB) + } + + panic(fmt.Sprintf("invalid key prefix %X", kvA.Key[:1])) + } +} diff --git a/x/plan/simulation/genesis.go b/x/plan/simulation/genesis.go new file mode 100644 index 00000000..075135a7 --- /dev/null +++ b/x/plan/simulation/genesis.go @@ -0,0 +1,13 @@ +package simulation + +import ( + "github.com/cosmos/cosmos-sdk/types/module" + + "github.com/sentinel-official/hub/x/plan/types" +) + +func RandomizedGenesisState(state *module.SimulationState) types.GenesisState { + return types.NewGenesisState( + RandomGenesisPlans(state.Rand), + ) +} diff --git a/x/plan/simulation/operations.go b/x/plan/simulation/operations.go new file mode 100644 index 00000000..9692e67c --- /dev/null +++ b/x/plan/simulation/operations.go @@ -0,0 +1,392 @@ +package simulation + +import ( + "math/rand" + "time" + + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/simapp/helpers" + "github.com/cosmos/cosmos-sdk/simapp/params" + sdk "github.com/cosmos/cosmos-sdk/types" + simulationtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/cosmos/cosmos-sdk/x/simulation" + + hubtypes "github.com/sentinel-official/hub/types" + simulationhubtypes "github.com/sentinel-official/hub/types/simulation" + "github.com/sentinel-official/hub/x/plan/expected" + "github.com/sentinel-official/hub/x/plan/keeper" + "github.com/sentinel-official/hub/x/plan/types" +) + +var ( + OperationWeightMsgAddRequest = "op_weight_" + types.TypeMsgAddRequest + OperationWeightMsgSetStatusRequest = "op_weight_" + types.TypeMsgSetStatusRequest + OperationWeightMsgAddNodeRequest = "op_weight_" + types.TypeMsgAddNodeRequest + OperationWeightMsgRemoveNodeRequest = "op_weight_" + types.TypeMsgRemoveNodeRequest +) + +func WeightedOperations( + params simulationtypes.AppParams, + cdc codec.JSONMarshaler, + ak expected.AccountKeeper, + bk expected.BankKeeper, + k keeper.Keeper, +) simulation.WeightedOperations { + var ( + weightMsgAddRequest int + weightMsgSetStatusRequest int + weightMsgAddNodeRequest int + weightMsgRemoveNodeRequest int + ) + + params.GetOrGenerate( + cdc, + OperationWeightMsgAddRequest, + &weightMsgAddRequest, + nil, + func(_ *rand.Rand) { + weightMsgAddRequest = 100 + }, + ) + params.GetOrGenerate( + cdc, + OperationWeightMsgSetStatusRequest, + &weightMsgSetStatusRequest, + nil, + func(_ *rand.Rand) { + weightMsgSetStatusRequest = 100 + }, + ) + params.GetOrGenerate( + cdc, + OperationWeightMsgAddNodeRequest, + &weightMsgAddNodeRequest, + nil, + func(_ *rand.Rand) { + weightMsgAddNodeRequest = 100 + }, + ) + params.GetOrGenerate( + cdc, + OperationWeightMsgRemoveNodeRequest, + &weightMsgRemoveNodeRequest, + nil, + func(_ *rand.Rand) { + weightMsgRemoveNodeRequest = 100 + }, + ) + + return simulation.WeightedOperations{ + simulation.NewWeightedOperation( + weightMsgAddRequest, + SimulateMsgAddRequest(ak, bk, k), + ), + simulation.NewWeightedOperation( + weightMsgSetStatusRequest, + SimulateMsgSetStatusRequest(ak, bk, k), + ), + simulation.NewWeightedOperation( + weightMsgAddNodeRequest, + SimulateMsgAddNodeRequest(ak, bk, k), + ), + simulation.NewWeightedOperation( + weightMsgRemoveNodeRequest, + SimulateMsgRemoveNodeRequest(ak, bk, k), + ), + } +} + +func SimulateMsgAddRequest(ak expected.AccountKeeper, bk expected.BankKeeper, k keeper.Keeper) simulationtypes.Operation { + return func( + r *rand.Rand, + app *baseapp.BaseApp, + ctx sdk.Context, + accounts []simulationtypes.Account, + chainID string, + ) (simulationtypes.OperationMsg, []simulationtypes.FutureOperation, error) { + var ( + rFrom, _ = simulationtypes.RandomAcc(r, accounts) + from = ak.GetAccount(ctx, rFrom.Address) + ) + + found := k.HasProvider(ctx, hubtypes.ProvAddress(from.GetAddress())) + if !found { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgAddRequest, "provider does not exist"), nil, nil + } + + balance := bk.SpendableCoins(ctx, from.GetAddress()) + if !balance.IsAnyNegative() { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgAddRequest, "balance is negative"), nil, nil + } + + fees, err := simulationtypes.RandomFees(r, ctx, balance) + if err != nil { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgAddRequest, err.Error()), nil, err + } + + var ( + price = simulationhubtypes.RandomCoins( + r, + sdk.NewCoins( + sdk.NewInt64Coin( + sdk.DefaultBondDenom, + MaxPlanPriceAmount, + ), + ), + ) + validity = time.Duration(r.Int63n(MaxPlanValidity)) * time.Minute + bytes = sdk.NewInt(r.Int63n(MaxPlanBytes)) + ) + + var ( + txConfig = params.MakeTestEncodingConfig().TxConfig + message = types.NewMsgAddRequest( + hubtypes.ProvAddress(from.GetAddress()), + price, + validity, + bytes, + ) + ) + + txn, err := helpers.GenTx( + txConfig, + []sdk.Msg{message}, + fees, + helpers.DefaultGenTxGas, + chainID, + []uint64{from.GetAccountNumber()}, + []uint64{from.GetSequence()}, + rFrom.PrivKey, + ) + if err != nil { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgAddRequest, err.Error()), nil, err + } + + _, _, err = app.Deliver(txConfig.TxEncoder(), txn) + if err != nil { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgAddRequest, err.Error()), nil, err + } + + return simulationtypes.NewOperationMsg(message, true, ""), nil, nil + } +} + +func SimulateMsgSetStatusRequest(ak expected.AccountKeeper, bk expected.BankKeeper, k keeper.Keeper) simulationtypes.Operation { + return func( + r *rand.Rand, + app *baseapp.BaseApp, + ctx sdk.Context, + accounts []simulationtypes.Account, + chainID string, + ) (simulationtypes.OperationMsg, []simulationtypes.FutureOperation, error) { + var ( + rFrom, _ = simulationtypes.RandomAcc(r, accounts) + from = ak.GetAccount(ctx, rFrom.Address) + ) + + plans := k.GetPlansForProvider(ctx, hubtypes.ProvAddress(from.GetAddress()), 0, 0) + if len(plans) == 0 { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgSetStatusRequest, "plans for provider does not exist"), nil, nil + } + + balance := bk.SpendableCoins(ctx, from.GetAddress()) + if !balance.IsAnyNegative() { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgSetStatusRequest, "balance is negative"), nil, nil + } + + fees, err := simulationtypes.RandomFees(r, ctx, balance) + if err != nil { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgSetStatusRequest, err.Error()), nil, err + } + + var ( + id = plans[r.Intn(len(plans))].Id + status = hubtypes.StatusActive + ) + + if r.Int63n(2) == 0 { + status = hubtypes.StatusInactive + } + + var ( + txConfig = params.MakeTestEncodingConfig().TxConfig + message = types.NewMsgSetStatusRequest( + hubtypes.ProvAddress(from.GetAddress()), + id, + status, + ) + ) + + txn, err := helpers.GenTx( + txConfig, + []sdk.Msg{message}, + fees, + helpers.DefaultGenTxGas, + chainID, + []uint64{from.GetAccountNumber()}, + []uint64{from.GetSequence()}, + rFrom.PrivKey, + ) + if err != nil { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgSetStatusRequest, err.Error()), nil, err + } + + _, _, err = app.Deliver(txConfig.TxEncoder(), txn) + if err != nil { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgSetStatusRequest, err.Error()), nil, err + } + + return simulationtypes.NewOperationMsg(message, true, ""), nil, nil + } +} + +func SimulateMsgAddNodeRequest(ak expected.AccountKeeper, bk expected.BankKeeper, k keeper.Keeper) simulationtypes.Operation { + return func( + r *rand.Rand, + app *baseapp.BaseApp, + ctx sdk.Context, + accounts []simulationtypes.Account, + chainID string, + ) (simulationtypes.OperationMsg, []simulationtypes.FutureOperation, error) { + var ( + rFrom, _ = simulationtypes.RandomAcc(r, accounts) + from = ak.GetAccount(ctx, rFrom.Address) + rAddress, _ = simulationtypes.RandomAcc(r, accounts) + address = ak.GetAccount(ctx, rAddress.Address) + ) + + node, found := k.GetNode(ctx, hubtypes.NodeAddress(address.GetAddress())) + if !found { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgAddNodeRequest, "node does not exist"), nil, nil + } + if node.Provider != hubtypes.ProvAddress(from.GetAddress()).String() { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgAddNodeRequest, "node has different provider"), nil, nil + } + + plans := k.GetPlansForProvider(ctx, hubtypes.ProvAddress(from.GetAddress()), 0, 0) + if len(plans) == 0 { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgAddNodeRequest, "plans for provider does not exist"), nil, nil + } + + var ( + id = plans[r.Intn(len(plans))].Id + ) + + found = k.HasNodeForPlan(ctx, id, hubtypes.NodeAddress(address.GetAddress())) + if found { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgAddNodeRequest, "node for plan already exists"), nil, nil + } + + balance := bk.SpendableCoins(ctx, from.GetAddress()) + if !balance.IsAnyNegative() { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgAddNodeRequest, "balance is negative"), nil, nil + } + + fees, err := simulationtypes.RandomFees(r, ctx, balance) + if err != nil { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgAddNodeRequest, err.Error()), nil, err + } + + var ( + txConfig = params.MakeTestEncodingConfig().TxConfig + message = types.NewMsgAddNodeRequest( + hubtypes.ProvAddress(from.GetAddress()), + id, + hubtypes.NodeAddress(address.GetAddress()), + ) + ) + + txn, err := helpers.GenTx( + txConfig, + []sdk.Msg{message}, + fees, + helpers.DefaultGenTxGas, + chainID, + []uint64{from.GetAccountNumber()}, + []uint64{from.GetSequence()}, + rFrom.PrivKey, + ) + if err != nil { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgAddNodeRequest, err.Error()), nil, err + } + + _, _, err = app.Deliver(txConfig.TxEncoder(), txn) + if err != nil { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgAddNodeRequest, err.Error()), nil, err + } + + return simulationtypes.NewOperationMsg(message, true, ""), nil, nil + } +} + +func SimulateMsgRemoveNodeRequest(ak expected.AccountKeeper, bk expected.BankKeeper, k keeper.Keeper) simulationtypes.Operation { + return func( + r *rand.Rand, + app *baseapp.BaseApp, + ctx sdk.Context, + accounts []simulationtypes.Account, + chainID string, + ) (simulationtypes.OperationMsg, []simulationtypes.FutureOperation, error) { + var ( + rFrom, _ = simulationtypes.RandomAcc(r, accounts) + from = ak.GetAccount(ctx, rFrom.Address) + rAddress, _ = simulationtypes.RandomAcc(r, accounts) + address = ak.GetAccount(ctx, rAddress.Address) + ) + + plans := k.GetPlansForProvider(ctx, hubtypes.ProvAddress(from.GetAddress()), 0, 0) + if len(plans) == 0 { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgRemoveNodeRequest, "plans for provider does not exist"), nil, nil + } + + var ( + id = plans[r.Intn(len(plans))].Id + ) + + found := k.HasNodeForPlan(ctx, id, hubtypes.NodeAddress(address.GetAddress())) + if !found { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgRemoveNodeRequest, "node for plan does not exist"), nil, nil + } + + balance := bk.SpendableCoins(ctx, from.GetAddress()) + if !balance.IsAnyNegative() { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgRemoveNodeRequest, "balance is negative"), nil, nil + } + + fees, err := simulationtypes.RandomFees(r, ctx, balance) + if err != nil { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgRemoveNodeRequest, err.Error()), nil, err + } + + var ( + txConfig = params.MakeTestEncodingConfig().TxConfig + message = types.NewMsgRemoveNodeRequest( + from.GetAddress(), + id, + hubtypes.NodeAddress(address.GetAddress()), + ) + ) + + txn, err := helpers.GenTx( + txConfig, + []sdk.Msg{message}, + fees, + helpers.DefaultGenTxGas, + chainID, + []uint64{from.GetAccountNumber()}, + []uint64{from.GetSequence()}, + rFrom.PrivKey, + ) + if err != nil { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgRemoveNodeRequest, err.Error()), nil, err + } + + _, _, err = app.Deliver(txConfig.TxEncoder(), txn) + if err != nil { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgRemoveNodeRequest, err.Error()), nil, err + } + + return simulationtypes.NewOperationMsg(message, true, ""), nil, nil + } +} diff --git a/x/plan/simulation/rand.go b/x/plan/simulation/rand.go new file mode 100644 index 00000000..34b23bec --- /dev/null +++ b/x/plan/simulation/rand.go @@ -0,0 +1,106 @@ +package simulation + +import ( + "math" + "math/rand" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + + hubtypes "github.com/sentinel-official/hub/types" + simulationhubtypes "github.com/sentinel-official/hub/types/simulation" + "github.com/sentinel-official/hub/x/plan/types" +) + +const ( + MaxPlanId = 1 << 18 + MaxPlans = 1 << 10 + MaxPlanPriceAmount = 1 << 10 + MaxPlanValidity = 1 << 18 + MaxPlanBytes = math.MaxInt64 +) + +func RandomPlan(r *rand.Rand, items types.Plans) types.Plan { + if len(items) == 0 { + return types.Plan{} + } + + return items[r.Intn(len(items))] +} + +func RandomPlans(r *rand.Rand) types.Plans { + var ( + items = make(types.Plans, 0, r.Intn(MaxPlans)) + duplicates = make(map[uint64]bool) + ) + + for len(items) < cap(items) { + id := uint64(r.Int63n(MaxPlanId)) + if duplicates[id] { + continue + } + + var ( + price = simulationhubtypes.RandomCoins( + r, + sdk.NewCoins( + sdk.NewInt64Coin( + sdk.DefaultBondDenom, + MaxPlanPriceAmount, + ), + ), + ) + validity = time.Duration(r.Int63n(MaxPlanValidity)) * time.Minute + bytes = sdk.NewInt(r.Int63n(MaxPlanBytes)) + status = hubtypes.StatusActive + statusAt = time.Now() + ) + + if rand.Intn(2) == 0 { + status = hubtypes.StatusInactive + } + + duplicates[id] = true + items = append( + items, + types.Plan{ + Id: id, + Provider: "", + Price: price, + Validity: validity, + Bytes: bytes, + Status: status, + StatusAt: statusAt, + }, + ) + } + + return items +} + +func RandomGenesisPlan(r *rand.Rand, items types.GenesisPlans) types.GenesisPlan { + if len(items) == 0 { + return types.GenesisPlan{} + } + + return items[r.Intn(len(items))] +} + +func RandomGenesisPlans(r *rand.Rand) types.GenesisPlans { + var ( + rItems = RandomPlans(r) + items = make(types.GenesisPlans, 0, len(rItems)) + ) + + for _, item := range rItems { + items = append( + items, + types.GenesisPlan{ + Plan: item, + Nodes: nil, + }, + ) + } + + return items +} diff --git a/x/plan/types/errors.go b/x/plan/types/errors.go index 5fbee36c..93d2dde8 100644 --- a/x/plan/types/errors.go +++ b/x/plan/types/errors.go @@ -5,10 +5,20 @@ import ( ) var ( - ErrorInvalidField = errors.Register(ModuleName, 101, "invalid field") - ErrorProviderDoesNotExist = errors.Register(ModuleName, 102, "provider does not exist") - ErrorPlanDoesNotExist = errors.Register(ModuleName, 103, "plan does not exist") - ErrorNodeDoesNotExist = errors.Register(ModuleName, 104, "node does not exist") - ErrorUnauthorized = errors.Register(ModuleName, 105, "unauthorized") - DuplicateNodeForPlan = errors.Register(ModuleName, 106, "duplicate node for plan") + ErrorInvalidField = errors.Register(ModuleName, 101, "invalid field") + ErrorInvalidFrom = errors.Register(ModuleName, 102, "invalid from") + ErrorInvalidPrice = errors.Register(ModuleName, 103, "invalid price") + ErrorInvalidValidity = errors.Register(ModuleName, 104, "invalid validity") + ErrorInvalidBytes = errors.Register(ModuleName, 105, "invalid bytes") + ErrorInvalidId = errors.Register(ModuleName, 106, "invalid id") + ErrorInvalidStatus = errors.Register(ModuleName, 107, "invalid status") + ErrorInvalidAddress = errors.Register(ModuleName, 108, "invalid address") +) + +var ( + ErrorProviderDoesNotExist = errors.Register(ModuleName, 201, "provider does not exist") + ErrorPlanDoesNotExist = errors.Register(ModuleName, 202, "plan does not exist") + ErrorNodeDoesNotExist = errors.Register(ModuleName, 203, "node does not exist") + ErrorUnauthorized = errors.Register(ModuleName, 204, "unauthorized") + DuplicateNodeForPlan = errors.Register(ModuleName, 205, "duplicate node for plan") ) diff --git a/x/plan/types/genesis.go b/x/plan/types/genesis.go index 3a7609e5..222a2938 100644 --- a/x/plan/types/genesis.go +++ b/x/plan/types/genesis.go @@ -19,32 +19,31 @@ func DefaultGenesisState() GenesisState { } func ValidateGenesis(state GenesisState) error { - for _, item := range state { - if err := item.Plan.Validate(); err != nil { - return err - } - } - plans := make(map[uint64]bool) for _, item := range state { - id := item.Plan.Id - if plans[id] { - return fmt.Errorf("duplicate plan id %d", id) + if plans[item.Plan.Id] { + return fmt.Errorf("found duplicate plan for id %d", item.Plan.Id) } - plans[id] = true + plans[item.Plan.Id] = true } for _, item := range state { nodes := make(map[string]bool) for _, address := range item.Nodes { if nodes[address] { - return fmt.Errorf("duplicate node for plan %d", item.Plan.Id) + return fmt.Errorf("found duplicate node %s for plan %d", address, item.Plan.Id) } nodes[address] = true } } + for _, item := range state { + if err := item.Plan.Validate(); err != nil { + return err + } + } + return nil } diff --git a/x/plan/types/genesis_test.go b/x/plan/types/genesis_test.go new file mode 100644 index 00000000..13ed40fb --- /dev/null +++ b/x/plan/types/genesis_test.go @@ -0,0 +1,45 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestDefaultGenesisState(t *testing.T) { + var ( + state GenesisState + ) + + state = DefaultGenesisState() + require.Equal(t, GenesisState(nil), state) +} + +func TestNewGenesisState(t *testing.T) { + var ( + plans GenesisPlans + state GenesisState + ) + + state = NewGenesisState(nil) + require.Nil(t, state) + require.Len(t, state, 0) + + state = NewGenesisState(plans) + require.Equal(t, GenesisState(nil), state) + require.Len(t, state, 0) + + plans = append(plans, + GenesisPlan{}, + ) + state = NewGenesisState(plans) + require.Equal(t, GenesisState(plans), state) + require.Len(t, state, 1) + + plans = append(plans, + GenesisPlan{}, + ) + state = NewGenesisState(plans) + require.Equal(t, GenesisState(plans), state) + require.Len(t, state, 2) +} diff --git a/x/plan/types/keys.go b/x/plan/types/keys.go index 100c91f7..97aa8fda 100644 --- a/x/plan/types/keys.go +++ b/x/plan/types/keys.go @@ -16,6 +16,13 @@ var ( StoreKey = ModuleName ) +var ( + TypeMsgAddRequest = ModuleName + ":add" + TypeMsgSetStatusRequest = ModuleName + ":set_status" + TypeMsgAddNodeRequest = ModuleName + ":add_node" + TypeMsgRemoveNodeRequest = ModuleName + ":remove_node" +) + var ( EventModuleName = EventModule{Name: ModuleName} ) diff --git a/x/plan/types/msg.go b/x/plan/types/msg.go index f3d7a415..4f2bae09 100644 --- a/x/plan/types/msg.go +++ b/x/plan/types/msg.go @@ -35,23 +35,31 @@ func (m *MsgAddRequest) Type() string { } func (m *MsgAddRequest) ValidateBasic() error { + if m.From == "" { + return errors.Wrap(ErrorInvalidFrom, "from cannot be empty") + } if _, err := hubtypes.ProvAddressFromBech32(m.From); err != nil { - return errors.Wrapf(ErrorInvalidField, "%s", "from") + return errors.Wrapf(ErrorInvalidFrom, "%s", err) } - - // Price can be nil. If not, it should be valid - if m.Price != nil && !m.Price.IsValid() { - return errors.Wrapf(ErrorInvalidField, "%s", "price") + if m.Price != nil { + if m.Price.Len() == 0 { + return errors.Wrap(ErrorInvalidPrice, "price cannot be empty") + } + if !m.Price.IsValid() { + return errors.Wrap(ErrorInvalidPrice, "price must be valid") + } } - - // Validity should be positive - if m.Validity <= 0 { - return errors.Wrapf(ErrorInvalidField, "%s", "validity") + if m.Validity < 0 { + return errors.Wrap(ErrorInvalidValidity, "validity cannot be negative") } - - // Bytes should be positive - if !m.Bytes.IsPositive() { - return errors.Wrapf(ErrorInvalidField, "%s", "bytes") + if m.Validity == 0 { + return errors.Wrap(ErrorInvalidValidity, "validity cannot be zero") + } + if m.Bytes.IsNegative() { + return errors.Wrap(ErrorInvalidBytes, "bytes cannot be negative") + } + if m.Bytes.IsZero() { + return errors.Wrap(ErrorInvalidBytes, "bytes cannot be zero") } return nil @@ -87,18 +95,17 @@ func (m *MsgSetStatusRequest) Type() string { } func (m *MsgSetStatusRequest) ValidateBasic() error { + if m.From == "" { + return errors.Wrap(ErrorInvalidFrom, "from cannot be empty") + } if _, err := hubtypes.ProvAddressFromBech32(m.From); err != nil { - return errors.Wrapf(ErrorInvalidField, "%s", "from") + return errors.Wrapf(ErrorInvalidFrom, "%s", err) } - - // Id shouldn't be zero if m.Id == 0 { - return errors.Wrapf(ErrorInvalidField, "%s", "id") + return errors.Wrap(ErrorInvalidId, "id cannot be zero") } - - // Status should be either Active or Inactive if !m.Status.Equal(hubtypes.StatusActive) && !m.Status.Equal(hubtypes.StatusInactive) { - return errors.Wrapf(ErrorInvalidField, "%s", "status") + return errors.Wrap(ErrorInvalidStatus, "status must be either active or inactive") } return nil @@ -134,18 +141,20 @@ func (m *MsgAddNodeRequest) Type() string { } func (m *MsgAddNodeRequest) ValidateBasic() error { + if m.From == "" { + return errors.Wrap(ErrorInvalidFrom, "from cannot be empty") + } if _, err := hubtypes.ProvAddressFromBech32(m.From); err != nil { - return errors.Wrapf(ErrorInvalidField, "%s", "from") + return errors.Wrapf(ErrorInvalidFrom, "%s", err) } - - // Id shouldn't be zero if m.Id == 0 { - return errors.Wrapf(ErrorInvalidField, "%s", "id") + return errors.Wrap(ErrorInvalidId, "id cannot be zero") + } + if m.Address == "" { + return errors.Wrap(ErrorInvalidAddress, "address cannot be empty") } - - // Address shouldn't be nil or empty if _, err := hubtypes.NodeAddressFromBech32(m.Address); err != nil { - return errors.Wrapf(ErrorInvalidField, "%s", "address") + return errors.Wrapf(ErrorInvalidAddress, "%s", err) } return nil @@ -181,18 +190,20 @@ func (m *MsgRemoveNodeRequest) Type() string { } func (m *MsgRemoveNodeRequest) ValidateBasic() error { - if _, err := sdk.AccAddressFromBech32(m.From); err != nil { - return errors.Wrapf(ErrorInvalidField, "%s", "from") + if m.From == "" { + return errors.Wrap(ErrorInvalidFrom, "from cannot be empty") + } + if _, err := hubtypes.ProvAddressFromBech32(m.From); err != nil { + return errors.Wrapf(ErrorInvalidFrom, "%s", err) } - - // Id shouldn't be zero if m.Id == 0 { - return errors.Wrapf(ErrorInvalidField, "%s", "id") + return errors.Wrap(ErrorInvalidId, "id cannot be zero") + } + if m.Address == "" { + return errors.Wrap(ErrorInvalidAddress, "address cannot be empty") } - - // Address should be valid if _, err := hubtypes.NodeAddressFromBech32(m.Address); err != nil { - return errors.Wrapf(ErrorInvalidField, "%s", "address") + return errors.Wrapf(ErrorInvalidAddress, "%s", err) } return nil diff --git a/x/plan/types/msg_test.go b/x/plan/types/msg_test.go new file mode 100644 index 00000000..ab2cb8b5 --- /dev/null +++ b/x/plan/types/msg_test.go @@ -0,0 +1,590 @@ +package types + +import ( + "testing" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + + hubtypes "github.com/sentinel-official/hub/types" +) + +func TestMsgAddRequest_ValidateBasic(t *testing.T) { + type fields struct { + From string + Price sdk.Coins + Validity time.Duration + Bytes sdk.Int + } + tests := []struct { + name string + fields fields + wantErr bool + }{ + { + "empty address", + fields{ + From: "", + }, + true, + }, + { + "invalid address", + fields{ + From: "invalid", + }, + true, + }, + { + "invalid prefix address", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + }, + true, + }, + { + "10 bytes address", + fields{ + From: "sentprov1qypqxpq9qcrsszgsutj8xr", + }, + true, + }, + { + "20 bytes address", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + }, + true, + }, + { + "30 bytes address", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfqyy3zxfp9ycnjs2fsh33zgx", + }, + true, + }, + { + "nil price", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Price: nil, + }, + true, + }, + { + "empty price", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Price: sdk.Coins{}, + }, + true, + }, + { + "empty denom price", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Price: sdk.Coins{sdk.Coin{Denom: ""}}, + }, + true, + }, + { + "invalid denom price", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Price: sdk.Coins{sdk.Coin{Denom: "o"}}, + }, + true, + }, + { + "negative amount price", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(-1000)}}, + }, + true, + }, + { + "zero amount price", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(0)}}, + }, + true, + }, + { + "positive amount price", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}}, + }, + true, + }, + { + "negative validity", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}}, + Validity: -1000, + }, + true, + }, + { + "zero validity", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}}, + Validity: 0, + }, + true, + }, + { + "positive validity", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}}, + Validity: 1000, + Bytes: sdk.NewInt(0), + }, + true, + }, + { + "negative bytes", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}}, + Validity: 1000, + Bytes: sdk.NewInt(-1000), + }, + true, + }, + { + "zero bytes", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}}, + Validity: 1000, + Bytes: sdk.NewInt(0), + }, + true, + }, + { + "positive bytes", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}}, + Validity: 1000, + Bytes: sdk.NewInt(1000), + }, + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &MsgAddRequest{ + From: tt.fields.From, + Price: tt.fields.Price, + Validity: tt.fields.Validity, + Bytes: tt.fields.Bytes, + } + if err := m.ValidateBasic(); (err != nil) != tt.wantErr { + t.Errorf("ValidateBasic() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestMsgSetStatusRequest_ValidateBasic(t *testing.T) { + type fields struct { + From string + Id uint64 + Status hubtypes.Status + } + tests := []struct { + name string + fields fields + wantErr bool + }{ + { + "empty address", + fields{ + From: "", + }, + true, + }, + { + "invalid address", + fields{ + From: "invalid", + }, + true, + }, + { + "invalid prefix address", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + }, + true, + }, + { + "10 bytes address", + fields{ + From: "sentprov1qypqxpq9qcrsszgsutj8xr", + }, + true, + }, + { + "20 bytes address", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + }, + true, + }, + { + "30 bytes address", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfqyy3zxfp9ycnjs2fsh33zgx", + }, + true, + }, + { + "zero id", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Id: 0, + }, + true, + }, + { + "positive id", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Id: 1000, + }, + true, + }, + { + "unknown status", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Id: 1000, + Status: hubtypes.StatusUnknown, + }, + true, + }, + { + "active status", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Id: 1000, + Status: hubtypes.StatusActive, + }, + false, + }, + { + "inactive pending status", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Id: 1000, + Status: hubtypes.StatusInactivePending, + }, + true, + }, + { + "inactive status", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Id: 1000, + Status: hubtypes.StatusInactive, + }, + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &MsgSetStatusRequest{ + From: tt.fields.From, + Id: tt.fields.Id, + Status: tt.fields.Status, + } + if err := m.ValidateBasic(); (err != nil) != tt.wantErr { + t.Errorf("ValidateBasic() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestMsgAddNodeRequest_ValidateBasic(t *testing.T) { + type fields struct { + From string + Id uint64 + Address string + } + tests := []struct { + name string + fields fields + wantErr bool + }{ + { + "empty address", + fields{ + From: "", + }, + true, + }, + { + "invalid address", + fields{ + From: "invalid", + }, + true, + }, + { + "invalid prefix address", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + }, + true, + }, + { + "10 bytes address", + fields{ + From: "sentprov1qypqxpq9qcrsszgsutj8xr", + }, + true, + }, + { + "20 bytes address", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + }, + true, + }, + { + "30 bytes address", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfqyy3zxfp9ycnjs2fsh33zgx", + }, + true, + }, + { + "zero id", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Id: 0, + }, + true, + }, + { + "positive id", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Id: 1000, + }, + true, + }, + { + "empty address", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Id: 1000, + Address: "", + }, + true, + }, + { + "invalid address", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Id: 1000, + Address: "invalid", + }, + true, + }, + { + "invalid prefix address", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Id: 1000, + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + }, + true, + }, + { + "10 bytes address", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Id: 1000, + Address: "sentnode1qypqxpq9qcrsszgse4wwrm", + }, + true, + }, + { + "20 bytes address", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Id: 1000, + Address: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + }, + false, + }, + { + "30 bytes address", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Id: 1000, + Address: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqyy3zxfp9ycnjs2fsxqglcv", + }, + true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &MsgAddNodeRequest{ + From: tt.fields.From, + Id: tt.fields.Id, + Address: tt.fields.Address, + } + if err := m.ValidateBasic(); (err != nil) != tt.wantErr { + t.Errorf("ValidateBasic() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestMsgRemoveNodeRequest_ValidateBasic(t *testing.T) { + type fields struct { + From string + Id uint64 + Address string + } + tests := []struct { + name string + fields fields + wantErr bool + }{ + { + "empty address", + fields{ + From: "", + }, + true, + }, + { + "invalid address", + fields{ + From: "invalid", + }, + true, + }, + { + "invalid prefix address", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + }, + true, + }, + { + "10 bytes address", + fields{ + From: "sentprov1qypqxpq9qcrsszgsutj8xr", + }, + true, + }, + { + "20 bytes address", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + }, + true, + }, + { + "30 bytes address", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfqyy3zxfp9ycnjs2fsh33zgx", + }, + true, + }, + { + "zero id", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Id: 0, + }, + true, + }, + { + "positive id", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Id: 1000, + }, + true, + }, + { + "empty address", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Id: 1000, + Address: "", + }, + true, + }, + { + "invalid address", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Id: 1000, + Address: "invalid", + }, + true, + }, + { + "invalid prefix address", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Id: 1000, + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + }, + true, + }, + { + "10 bytes address", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Id: 1000, + Address: "sentnode1qypqxpq9qcrsszgse4wwrm", + }, + true, + }, + { + "20 bytes address", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Id: 1000, + Address: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + }, + false, + }, + { + "30 bytes address", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Id: 1000, + Address: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqyy3zxfp9ycnjs2fsxqglcv", + }, + true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &MsgRemoveNodeRequest{ + From: tt.fields.From, + Id: tt.fields.Id, + Address: tt.fields.Address, + } + if err := m.ValidateBasic(); (err != nil) != tt.wantErr { + t.Errorf("ValidateBasic() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/x/plan/types/plan.go b/x/plan/types/plan.go index 69af5bb5..d0210a3f 100644 --- a/x/plan/types/plan.go +++ b/x/plan/types/plan.go @@ -4,16 +4,17 @@ import ( "fmt" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/errors" hubtypes "github.com/sentinel-official/hub/types" ) -func (p *Plan) GetProvider() hubtypes.ProvAddress { - if p.Provider == "" { +func (m *Plan) GetProvider() hubtypes.ProvAddress { + if m.Provider == "" { return nil } - address, err := hubtypes.ProvAddressFromBech32(p.Provider) + address, err := hubtypes.ProvAddressFromBech32(m.Provider) if err != nil { panic(err) } @@ -21,8 +22,8 @@ func (p *Plan) GetProvider() hubtypes.ProvAddress { return address } -func (p *Plan) PriceForDenom(d string) (sdk.Coin, bool) { - for _, coin := range p.Price { +func (m *Plan) PriceForDenom(d string) (sdk.Coin, bool) { + for _, coin := range m.Price { if coin.Denom == d { return coin, true } @@ -31,30 +32,46 @@ func (p *Plan) PriceForDenom(d string) (sdk.Coin, bool) { return sdk.Coin{}, false } -func (p *Plan) Validate() error { - if p.Id == 0 { - return fmt.Errorf("invalid id; expected positive value") +func (m *Plan) Validate() error { + if m.Id == 0 { + return fmt.Errorf("id cannot be zero") } - if _, err := hubtypes.ProvAddressFromBech32(p.Provider); err != nil { - return err + if m.Provider == "" { + return fmt.Errorf("provider cannot be empty") } - if p.Price != nil && !p.Price.IsValid() { - return fmt.Errorf("invalid price; expected non-nil and valid value") + if _, err := hubtypes.ProvAddressFromBech32(m.Provider); err != nil { + return errors.Wrapf(err, "invalid provider %s", m.Provider) } - if p.Validity <= 0 { - return fmt.Errorf("invalid validity; expected positive value") + if m.Price != nil { + if m.Price.Len() == 0 { + return fmt.Errorf("price cannot be empty") + } + if !m.Price.IsValid() { + return fmt.Errorf("price must be valid") + } + } + if m.Validity < 0 { + return fmt.Errorf("validity cannot be negative") } - if !p.Bytes.IsPositive() { - return fmt.Errorf("invalid bytes; expected positive value") + if m.Validity == 0 { + return fmt.Errorf("validity cannot be zero") } - if !p.Status.Equal(hubtypes.StatusActive) && !p.Status.Equal(hubtypes.StatusInactive) { - return fmt.Errorf("invalid status; exptected active or inactive") + if m.Bytes.IsNegative() { + return fmt.Errorf("bytes cannot be negative") } - if p.StatusAt.IsZero() { - return fmt.Errorf("invalid status at; expected non-zero value") + if m.Bytes.IsZero() { + return fmt.Errorf("bytes cannot be zero") + } + if !m.Status.Equal(hubtypes.StatusActive) && !m.Status.Equal(hubtypes.StatusInactive) { + return fmt.Errorf("status must be either active or inactive") + } + if m.StatusAt.IsZero() { + return fmt.Errorf("status_at cannot be zero") } return nil } -type Plans []Plan +type ( + Plans []Plan +) diff --git a/x/plan/types/plan_test.go b/x/plan/types/plan_test.go new file mode 100644 index 00000000..80144c3e --- /dev/null +++ b/x/plan/types/plan_test.go @@ -0,0 +1,447 @@ +package types + +import ( + "reflect" + "testing" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + + hubtypes "github.com/sentinel-official/hub/types" +) + +func TestPlan_GetProvider(t *testing.T) { + type fields struct { + Id uint64 + Provider string + Price sdk.Coins + Validity time.Duration + Bytes sdk.Int + Status hubtypes.Status + StatusAt time.Time + } + tests := []struct { + name string + fields fields + want hubtypes.ProvAddress + }{ + { + "empty", + fields{ + Provider: "", + }, + nil, + }, + { + "20 bytes", + fields{ + Provider: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + }, + hubtypes.ProvAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := &Plan{ + Id: tt.fields.Id, + Provider: tt.fields.Provider, + Price: tt.fields.Price, + Validity: tt.fields.Validity, + Bytes: tt.fields.Bytes, + Status: tt.fields.Status, + StatusAt: tt.fields.StatusAt, + } + if got := p.GetProvider(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("GetProvider() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestPlan_PriceForDenom(t *testing.T) { + type fields struct { + Id uint64 + Provider string + Price sdk.Coins + Validity time.Duration + Bytes sdk.Int + Status hubtypes.Status + StatusAt time.Time + } + type args struct { + d string + } + tests := []struct { + name string + fields fields + args args + want sdk.Coin + want1 bool + }{ + { + "nil price and empty denom", + fields{ + Price: nil, + }, + args{ + d: "", + }, + sdk.Coin{}, + false, + }, + { + "empty price and empty denom", + fields{ + Price: sdk.Coins{}, + }, + args{ + d: "", + }, + sdk.Coin{}, + false, + }, + { + "1one price and empty denom", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 1)}, + }, + args{ + d: "", + }, + sdk.Coin{}, + false, + }, + { + "1one price and one denom", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 1)}, + }, + args{ + d: "one", + }, + sdk.NewInt64Coin("one", 1), + true, + }, + { + "1one price and two denom", + fields{ + Price: sdk.Coins{sdk.NewInt64Coin("one", 1)}, + }, + args{ + d: "two", + }, + sdk.Coin{}, + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := &Plan{ + Id: tt.fields.Id, + Provider: tt.fields.Provider, + Price: tt.fields.Price, + Validity: tt.fields.Validity, + Bytes: tt.fields.Bytes, + Status: tt.fields.Status, + StatusAt: tt.fields.StatusAt, + } + got, got1 := p.PriceForDenom(tt.args.d) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("PriceForDenom() got = %v, want %v", got, tt.want) + } + if got1 != tt.want1 { + t.Errorf("PriceForDenom() got1 = %v, want %v", got1, tt.want1) + } + }) + } +} + +func TestPlan_Validate(t *testing.T) { + type fields struct { + Id uint64 + Provider string + Price sdk.Coins + Validity time.Duration + Bytes sdk.Int + Status hubtypes.Status + StatusAt time.Time + } + tests := []struct { + name string + fields fields + wantErr bool + }{ + { + "zero id", + fields{ + Id: 0, + }, + true, + }, + { + "empty provider", + fields{ + Id: 1000, + Provider: "", + }, + true, + }, + { + "invalid provider", + fields{ + Id: 1000, + Provider: "invalid", + }, + true, + }, + { + "invalid prefix provider", + fields{ + Id: 1000, + Provider: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + }, + true, + }, + { + "10 bytes provider", + fields{ + Id: 1000, + Provider: "sentprov1qypqxpq9qcrsszgsutj8xr", + }, + true, + }, + { + "20 bytes provider", + fields{ + Id: 1000, + Provider: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + }, + true, + }, + { + "30 bytes provider", + fields{ + Id: 1000, + Provider: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfqyy3zxfp9ycnjs2fsh33zgx", + }, + true, + }, + { + "nil price", + fields{ + Id: 1000, + Provider: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Price: nil, + }, + true, + }, + { + "empty price", + fields{ + Id: 1000, + Provider: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Price: sdk.Coins{}, + }, + true, + }, + { + "empty denom price", + fields{ + Id: 1000, + Provider: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Price: sdk.Coins{sdk.Coin{Denom: ""}}, + }, + true, + }, + { + "invalid denom price", + fields{ + Id: 1000, + Provider: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Price: sdk.Coins{sdk.Coin{Denom: "o"}}, + }, + true, + }, + { + "negative amount price", + fields{ + Id: 1000, + Provider: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(-1000)}}, + }, + true, + }, + { + "zero amount price", + fields{ + Id: 1000, + Provider: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(0)}}, + }, + true, + }, + { + "positive amount price", + fields{ + Id: 1000, + Provider: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}}, + }, + true, + }, + { + "negative validity", + fields{ + Id: 1000, + Provider: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}}, + Validity: -1000, + }, + true, + }, + { + "zero validity", + fields{ + Id: 1000, + Provider: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}}, + Validity: 0, + }, + true, + }, + { + "positive validity", + fields{ + Id: 1000, + Provider: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}}, + Validity: 1000, + Bytes: sdk.NewInt(0), + }, + true, + }, + { + "negative bytes", + fields{ + Id: 1000, + Provider: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}}, + Validity: 1000, + Bytes: sdk.NewInt(-1000), + }, + true, + }, + { + "zero bytes", + fields{ + Id: 1000, + Provider: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}}, + Validity: 1000, + Bytes: sdk.NewInt(0), + }, + true, + }, + { + "positive bytes", + fields{ + Id: 1000, + Provider: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}}, + Validity: 1000, + Bytes: sdk.NewInt(1000), + }, + true, + }, + { + "unknown status", + fields{ + Id: 1000, + Provider: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}}, + Validity: 1000, + Bytes: sdk.NewInt(1000), + Status: hubtypes.StatusUnknown, + }, + true, + }, + { + "active status", + fields{ + Id: 1000, + Provider: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}}, + Validity: 1000, + Bytes: sdk.NewInt(1000), + Status: hubtypes.StatusActive, + }, + true, + }, + { + "inactive pending status", + fields{ + Id: 1000, + Provider: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}}, + Validity: 1000, + Bytes: sdk.NewInt(1000), + Status: hubtypes.StatusInactivePending, + }, + true, + }, + { + "inactive status", + fields{ + Id: 1000, + Provider: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}}, + Validity: 1000, + Bytes: sdk.NewInt(1000), + Status: hubtypes.StatusInactive, + }, + true, + }, + { + "zero status_at", + fields{ + Id: 1000, + Provider: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}}, + Validity: 1000, + Bytes: sdk.NewInt(1000), + Status: hubtypes.StatusInactive, + StatusAt: time.Time{}, + }, + true, + }, + { + "now status_at", + fields{ + Id: 1000, + Provider: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Price: sdk.Coins{sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}}, + Validity: 1000, + Bytes: sdk.NewInt(1000), + Status: hubtypes.StatusInactive, + StatusAt: time.Now(), + }, + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := &Plan{ + Id: tt.fields.Id, + Provider: tt.fields.Provider, + Price: tt.fields.Price, + Validity: tt.fields.Validity, + Bytes: tt.fields.Bytes, + Status: tt.fields.Status, + StatusAt: tt.fields.StatusAt, + } + if err := p.Validate(); (err != nil) != tt.wantErr { + t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/x/provider/expected/keeper.go b/x/provider/expected/keeper.go index d7010fa2..c4cd0ebf 100644 --- a/x/provider/expected/keeper.go +++ b/x/provider/expected/keeper.go @@ -9,6 +9,10 @@ type AccountKeeper interface { GetAccount(ctx sdk.Context, address sdk.AccAddress) authtypes.AccountI } +type BankKeeper interface { + SpendableCoins(ctx sdk.Context, address sdk.AccAddress) sdk.Coins +} + type DistributionKeeper interface { FundCommunityPool(ctx sdk.Context, amount sdk.Coins, sender sdk.AccAddress) error } diff --git a/x/provider/genesis.go b/x/provider/genesis.go index 413d5648..ec539ce7 100644 --- a/x/provider/genesis.go +++ b/x/provider/genesis.go @@ -16,5 +16,8 @@ func InitGenesis(ctx sdk.Context, k keeper.Keeper, state *types.GenesisState) { } func ExportGenesis(ctx sdk.Context, k keeper.Keeper) *types.GenesisState { - return types.NewGenesisState(k.GetProviders(ctx, 0, 0), k.GetParams(ctx)) + return types.NewGenesisState( + k.GetProviders(ctx, 0, 0), + k.GetParams(ctx), + ) } diff --git a/x/provider/simulation/decoder.go b/x/provider/simulation/decoder.go new file mode 100644 index 00000000..cb5b1cd0 --- /dev/null +++ b/x/provider/simulation/decoder.go @@ -0,0 +1,25 @@ +package simulation + +import ( + "bytes" + "fmt" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/types/kv" + + "github.com/sentinel-official/hub/x/provider/types" +) + +func NewDecodeStore(cdc codec.Marshaler) func(kvA, kvB kv.Pair) string { + return func(kvA, kvB kv.Pair) string { + if bytes.Equal(kvA.Key[:1], types.ProviderKeyPrefix) { + var providerA, providerB types.Provider + cdc.MustUnmarshalBinaryBare(kvA.Value, &providerA) + cdc.MustUnmarshalBinaryBare(kvB.Value, &providerB) + + return fmt.Sprintf("%v\n%v", providerA, providerB) + } + + panic(fmt.Sprintf("invalid key prefix %X", kvA.Key[:1])) + } +} diff --git a/x/provider/simulation/genesis.go b/x/provider/simulation/genesis.go new file mode 100644 index 00000000..640da280 --- /dev/null +++ b/x/provider/simulation/genesis.go @@ -0,0 +1,34 @@ +package simulation + +import ( + "math/rand" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + + "github.com/sentinel-official/hub/x/provider/types" +) + +func RandomizedGenesisState(state *module.SimulationState) *types.GenesisState { + var ( + deposit sdk.Coin + ) + + state.AppParams.GetOrGenerate( + state.Cdc, + string(types.KeyDeposit), + &deposit, + state.Rand, + func(r *rand.Rand) { + deposit = sdk.NewInt64Coin( + sdk.DefaultBondDenom, + r.Int63n(MaxDepositAmount), + ) + }, + ) + + return types.NewGenesisState( + RandomProviders(state.Rand, state.Accounts), + types.NewParams(deposit), + ) +} diff --git a/x/provider/simulation/operations.go b/x/provider/simulation/operations.go new file mode 100644 index 00000000..1af2fe8a --- /dev/null +++ b/x/provider/simulation/operations.go @@ -0,0 +1,209 @@ +package simulation + +import ( + "math/rand" + + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/simapp/helpers" + "github.com/cosmos/cosmos-sdk/simapp/params" + sdk "github.com/cosmos/cosmos-sdk/types" + simulationtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/cosmos/cosmos-sdk/x/simulation" + + hubtypes "github.com/sentinel-official/hub/types" + "github.com/sentinel-official/hub/x/provider/expected" + "github.com/sentinel-official/hub/x/provider/keeper" + "github.com/sentinel-official/hub/x/provider/types" +) + +var ( + OperationWeightMsgRegisterRequest = "op_weight_" + types.TypeMsgRegisterRequest + OperationWeightMsgUpdateRequest = "op_weight_" + types.TypeMsgUpdateRequest +) + +func WeightedOperations( + params simulationtypes.AppParams, + cdc codec.JSONMarshaler, + ak expected.AccountKeeper, + bk expected.BankKeeper, + k keeper.Keeper, +) simulation.WeightedOperations { + var ( + weightMsgRegisterRequest int + weightMsgUpdateRequest int + ) + + params.GetOrGenerate( + cdc, + OperationWeightMsgRegisterRequest, + &weightMsgRegisterRequest, + nil, + func(_ *rand.Rand) { + weightMsgRegisterRequest = 100 + }, + ) + params.GetOrGenerate( + cdc, + OperationWeightMsgUpdateRequest, + &weightMsgUpdateRequest, + nil, + func(_ *rand.Rand) { + weightMsgUpdateRequest = 100 + }, + ) + + return simulation.WeightedOperations{ + simulation.NewWeightedOperation( + weightMsgRegisterRequest, + SimulateMsgRegisterRequest(ak, bk, k), + ), + simulation.NewWeightedOperation( + weightMsgUpdateRequest, + SimulateMsgUpdateRequest(ak, bk, k), + ), + } +} + +func SimulateMsgRegisterRequest(ak expected.AccountKeeper, bk expected.BankKeeper, k keeper.Keeper) simulationtypes.Operation { + return func( + r *rand.Rand, + app *baseapp.BaseApp, + ctx sdk.Context, + accounts []simulationtypes.Account, + chainID string, + ) (simulationtypes.OperationMsg, []simulationtypes.FutureOperation, error) { + var ( + rAccount, _ = simulationtypes.RandomAcc(r, accounts) + account = ak.GetAccount(ctx, rAccount.Address) + ) + + found := k.HasProvider(ctx, hubtypes.ProvAddress(account.GetAddress())) + if found { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgUpdateRequest, "provider already exists"), nil, nil + } + + balance := bk.SpendableCoins(ctx, account.GetAddress()) + if !balance.IsAnyNegative() { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgRegisterRequest, "balance is negative"), nil, nil + } + + fees, err := simulationtypes.RandomFees(r, ctx, balance) + if err != nil { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgRegisterRequest, err.Error()), nil, err + } + + deposit := k.Deposit(ctx) + if balance.Sub(fees).AmountOf(deposit.Denom).LT(deposit.Amount) { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgRegisterRequest, "balance is less than deposit"), nil, nil + } + + var ( + name = simulationtypes.RandStringOfLength(r, r.Intn(MaxNameLength)+8) + identity = simulationtypes.RandStringOfLength(r, r.Intn(MaxIdentityLength)) + website = simulationtypes.RandStringOfLength(r, r.Intn(MaxWebsiteLength)) + description = simulationtypes.RandStringOfLength(r, r.Intn(MaxDescriptionLength)) + ) + + var ( + txConfig = params.MakeTestEncodingConfig().TxConfig + message = types.NewMsgRegisterRequest( + account.GetAddress(), + name, + identity, + website, + description, + ) + ) + + txn, err := helpers.GenTx( + txConfig, + []sdk.Msg{message}, + fees, + helpers.DefaultGenTxGas, + chainID, + []uint64{account.GetAccountNumber()}, + []uint64{account.GetSequence()}, + rAccount.PrivKey, + ) + if err != nil { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgRegisterRequest, err.Error()), nil, err + } + + _, _, err = app.Deliver(txConfig.TxEncoder(), txn) + if err != nil { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgRegisterRequest, err.Error()), nil, err + } + + return simulationtypes.NewOperationMsg(message, true, ""), nil, nil + } +} + +func SimulateMsgUpdateRequest(ak expected.AccountKeeper, bk expected.BankKeeper, k keeper.Keeper) simulationtypes.Operation { + return func( + r *rand.Rand, + app *baseapp.BaseApp, + ctx sdk.Context, + accounts []simulationtypes.Account, + chainID string, + ) (simulationtypes.OperationMsg, []simulationtypes.FutureOperation, error) { + var ( + rAccount, _ = simulationtypes.RandomAcc(r, accounts) + account = ak.GetAccount(ctx, rAccount.Address) + ) + + found := k.HasProvider(ctx, hubtypes.ProvAddress(account.GetAddress())) + if !found { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgUpdateRequest, "provider does not exist"), nil, nil + } + + balance := bk.SpendableCoins(ctx, account.GetAddress()) + if !balance.IsAnyNegative() { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgUpdateRequest, "balance is negative"), nil, nil + } + + fees, err := simulationtypes.RandomFees(r, ctx, balance) + if err != nil { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgUpdateRequest, err.Error()), nil, err + } + + var ( + name = simulationtypes.RandStringOfLength(r, r.Intn(MaxNameLength+8)) + identity = simulationtypes.RandStringOfLength(r, r.Intn(MaxIdentityLength)) + website = simulationtypes.RandStringOfLength(r, r.Intn(MaxWebsiteLength)) + description = simulationtypes.RandStringOfLength(r, r.Intn(MaxDescriptionLength)) + ) + + var ( + txConfig = params.MakeTestEncodingConfig().TxConfig + message = types.NewMsgUpdateRequest( + hubtypes.ProvAddress(account.GetAddress()), + name, + identity, + website, + description, + ) + ) + + txn, err := helpers.GenTx( + txConfig, + []sdk.Msg{message}, + fees, + helpers.DefaultGenTxGas, + chainID, + []uint64{account.GetAccountNumber()}, + []uint64{account.GetSequence()}, + rAccount.PrivKey, + ) + if err != nil { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgUpdateRequest, err.Error()), nil, err + } + + _, _, err = app.Deliver(txConfig.TxEncoder(), txn) + if err != nil { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgUpdateRequest, err.Error()), nil, err + } + + return simulationtypes.NewOperationMsg(message, true, ""), nil, nil + } +} diff --git a/x/provider/simulation/params.go b/x/provider/simulation/params.go new file mode 100644 index 00000000..f4b4c774 --- /dev/null +++ b/x/provider/simulation/params.go @@ -0,0 +1,30 @@ +package simulation + +import ( + "math/rand" + + sdk "github.com/cosmos/cosmos-sdk/types" + simulationtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/cosmos/cosmos-sdk/x/simulation" + + "github.com/sentinel-official/hub/x/provider/types" +) + +const ( + MaxDepositAmount = 1 << 18 +) + +func ParamChanges(_ *rand.Rand) []simulationtypes.ParamChange { + return []simulationtypes.ParamChange{ + simulation.NewSimParamChange( + types.ModuleName, + string(types.KeyDeposit), + func(r *rand.Rand) string { + return sdk.NewInt64Coin( + sdk.DefaultBondDenom, + r.Int63n(MaxDepositAmount), + ).String() + }, + ), + } +} diff --git a/x/provider/simulation/rand.go b/x/provider/simulation/rand.go new file mode 100644 index 00000000..7b4befe9 --- /dev/null +++ b/x/provider/simulation/rand.go @@ -0,0 +1,64 @@ +package simulation + +import ( + "math/rand" + + simulationtypes "github.com/cosmos/cosmos-sdk/types/simulation" + + hubtypes "github.com/sentinel-official/hub/types" + "github.com/sentinel-official/hub/x/provider/types" +) + +const ( + MaxNameLength = 56 + MaxIdentityLength = 64 + MaxWebsiteLength = 64 + MaxDescriptionLength = 256 +) + +func RandomProvider(r *rand.Rand, items types.Providers) types.Provider { + if len(items) == 0 { + return types.Provider{} + } + + return items[r.Intn(len(items))] +} + +func RandomProviders(r *rand.Rand, accounts []simulationtypes.Account) types.Providers { + var ( + duplicates = make(map[string]bool) + items = make(types.Providers, 0, r.Intn(len(accounts))) + ) + + for len(items) < cap(items) { + var ( + account, _ = simulationtypes.RandomAcc(r, accounts) + address = hubtypes.ProvAddress(account.Address).String() + ) + + if duplicates[address] { + continue + } + + var ( + name = simulationtypes.RandStringOfLength(r, r.Intn(MaxNameLength)+8) + identity = simulationtypes.RandStringOfLength(r, r.Intn(MaxIdentityLength)) + website = simulationtypes.RandStringOfLength(r, r.Intn(MaxWebsiteLength)) + description = simulationtypes.RandStringOfLength(r, r.Intn(MaxDescriptionLength)) + ) + + duplicates[address] = true + items = append( + items, + types.Provider{ + Address: address, + Name: name, + Identity: identity, + Website: website, + Description: description, + }, + ) + } + + return items +} diff --git a/x/provider/types/errors.go b/x/provider/types/errors.go index 744c4176..118fc589 100644 --- a/x/provider/types/errors.go +++ b/x/provider/types/errors.go @@ -5,15 +5,15 @@ import ( ) var ( - ErrorMarshal = errors.Register(ModuleName, 101, "error occurred while marshalling") - ErrorUnmarshal = errors.Register(ModuleName, 102, "error occurred while unmarshalling") - ErrorUnknownMsgType = errors.Register(ModuleName, 103, "unknown message type") - ErrorUnknownQueryType = errors.Register(ModuleName, 104, "unknown query type") - ErrorInvalidFieldName = errors.Register(ModuleName, 105, "invalid field name; expected length between 1 and 64") - ErrorDuplicateProvider = errors.Register(ModuleName, 106, "duplicate provider") - ErrorProviderDoesNotExist = errors.Register(ModuleName, 107, "provider does not exist") - ErrorInvalidFieldFrom = errors.Register(ModuleName, 108, "invalid field from; expected a valid address") - ErrorInvalidFieldIdentity = errors.Register(ModuleName, 109, "invalid field identity; expected length between 0 and 64") - ErrorInvalidFieldWebsite = errors.Register(ModuleName, 110, "invalid field website; expected length between 0 and 64") - ErrorInvalidFieldDescription = errors.Register(ModuleName, 111, "invalid field description; expected length between 0 and 256") + ErrorInvalidField = errors.Register(ModuleName, 101, "invalid field") + ErrorInvalidFrom = errors.Register(ModuleName, 102, "invalid from") + ErrorInvalidName = errors.Register(ModuleName, 103, "invalid name") + ErrorInvalidIdentity = errors.Register(ModuleName, 104, "invalid identity") + ErrorInvalidWebsite = errors.Register(ModuleName, 105, "invalid website") + ErrorInvalidDescription = errors.Register(ModuleName, 106, "invalid description") +) + +var ( + ErrorDuplicateProvider = errors.Register(ModuleName, 201, "duplicate provider") + ErrorProviderDoesNotExist = errors.Register(ModuleName, 202, "provider does not exist") ) diff --git a/x/provider/types/genesis_test.go b/x/provider/types/genesis_test.go new file mode 100644 index 00000000..0a28feff --- /dev/null +++ b/x/provider/types/genesis_test.go @@ -0,0 +1,61 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestDefaultGenesisState(t *testing.T) { + var ( + state *GenesisState + ) + + state = DefaultGenesisState() + require.Equal(t, &GenesisState{ + Providers: nil, + Params: DefaultParams(), + }, state) +} + +func TestNewGenesisState(t *testing.T) { + var ( + params Params + providers Providers + state *GenesisState + ) + + state = NewGenesisState(nil, params) + require.Equal(t, &GenesisState{ + Providers: nil, + Params: params, + }, state) + require.Len(t, state.Providers, 0) + + state = NewGenesisState(providers, params) + require.Equal(t, &GenesisState{ + Providers: providers, + Params: params, + }, state) + require.Len(t, state.Providers, 0) + + providers = append(providers, + Provider{}, + ) + state = NewGenesisState(providers, params) + require.Equal(t, &GenesisState{ + Providers: providers, + Params: params, + }, state) + require.Len(t, state.Providers, 1) + + providers = append(providers, + Provider{}, + ) + state = NewGenesisState(providers, params) + require.Equal(t, &GenesisState{ + Providers: providers, + Params: params, + }, state) + require.Len(t, state.Providers, 2) +} diff --git a/x/provider/types/keys.go b/x/provider/types/keys.go index d53fbdba..c96007da 100644 --- a/x/provider/types/keys.go +++ b/x/provider/types/keys.go @@ -15,6 +15,11 @@ var ( StoreKey = ModuleName ) +var ( + TypeMsgRegisterRequest = ModuleName + ":register" + TypeMsgUpdateRequest = ModuleName + ":update" +) + var ( EventModuleName = EventModule{Name: ModuleName} ) diff --git a/x/provider/types/msg.go b/x/provider/types/msg.go index bf2db71b..a580b17b 100644 --- a/x/provider/types/msg.go +++ b/x/provider/types/msg.go @@ -1,9 +1,10 @@ package types import ( - "fmt" + "net/url" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/errors" hubtypes "github.com/sentinel-official/hub/types" ) @@ -28,32 +29,35 @@ func (m *MsgRegisterRequest) Route() string { } func (m *MsgRegisterRequest) Type() string { - return fmt.Sprintf("%s:register", ModuleName) + return TypeMsgRegisterRequest } func (m *MsgRegisterRequest) ValidateBasic() error { + if m.From == "" { + return errors.Wrap(ErrorInvalidFrom, "from cannot be empty") + } if _, err := sdk.AccAddressFromBech32(m.From); err != nil { - return ErrorInvalidFieldFrom + return errors.Wrapf(ErrorInvalidFrom, "%s", err) } - - // Name length should be between 1 and 64 - if len(m.Name) == 0 || len(m.Name) > 64 { - return ErrorInvalidFieldName + if m.Name == "" { + return errors.Wrap(ErrorInvalidName, "name cannot be empty") + } + if len(m.Name) > 64 { + return errors.Wrapf(ErrorInvalidName, "name length cannot be greater than %d", 64) } - - // Identity length should be between 0 and 64 if len(m.Identity) > 64 { - return ErrorInvalidFieldIdentity + return errors.Wrapf(ErrorInvalidIdentity, "identity length cannot be greater than %d", 64) } - - // Website length should be between 0 and 64 - if len(m.Website) > 64 { - return ErrorInvalidFieldWebsite + if m.Website != "" { + if len(m.Website) > 64 { + return errors.Wrapf(ErrorInvalidWebsite, "website length cannot be greater than %d", 64) + } + if _, err := url.ParseRequestURI(m.Website); err != nil { + return errors.Wrapf(ErrorInvalidWebsite, "%s", err) + } } - - // Description length should be between 0 and 256 if len(m.Description) > 256 { - return ErrorInvalidFieldDescription + return errors.Wrapf(ErrorInvalidDescription, "description length cannot be greater than %d", 256) } return nil @@ -87,32 +91,32 @@ func (m *MsgUpdateRequest) Route() string { } func (m *MsgUpdateRequest) Type() string { - return fmt.Sprintf("%s:update", ModuleName) + return TypeMsgUpdateRequest } func (m MsgUpdateRequest) ValidateBasic() error { + if m.From == "" { + return errors.Wrap(ErrorInvalidFrom, "from cannot be empty") + } if _, err := hubtypes.ProvAddressFromBech32(m.From); err != nil { - return ErrorInvalidFieldFrom + return errors.Wrapf(ErrorInvalidFrom, "%s", err) } - - // Name length should be between 0 and 64 if len(m.Name) > 64 { - return ErrorInvalidFieldName + return errors.Wrapf(ErrorInvalidName, "name length cannot be greater than %d", 64) } - - // Identity length should be between 0 and 64 if len(m.Identity) > 64 { - return ErrorInvalidFieldIdentity + return errors.Wrapf(ErrorInvalidIdentity, "identity length cannot be greater than %d", 64) } - - // Website length should be between 0 and 64 - if len(m.Website) > 64 { - return ErrorInvalidFieldWebsite + if m.Website != "" { + if len(m.Website) > 64 { + return errors.Wrapf(ErrorInvalidWebsite, "website length cannot be greater than %d", 64) + } + if _, err := url.ParseRequestURI(m.Website); err != nil { + return errors.Wrapf(ErrorInvalidWebsite, "%s", err) + } } - - // Description length should be between 0 and 256 if len(m.Description) > 256 { - return ErrorInvalidFieldDescription + return errors.Wrapf(ErrorInvalidDescription, "description length cannot be greater than %d", 256) } return nil diff --git a/x/provider/types/msg_test.go b/x/provider/types/msg_test.go index 189ec22e..9ce5539d 100644 --- a/x/provider/types/msg_test.go +++ b/x/provider/types/msg_test.go @@ -1,89 +1,397 @@ package types import ( - "errors" "strings" "testing" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -var ( - GT64 = strings.Repeat("sentinel", 9) - GT256 = strings.Repeat("sentinel", 33) ) -func TestMsgRegister_ValidateBasic(t *testing.T) { - correctAddress, err := sdk.AccAddressFromBech32("sent1grdunxx5jxd0ja75wt508sn6v39p70hhw53zs8") - if err != nil { - t.Errorf("failed: %s\n", err) +func TestMsgRegisterRequest_ValidateBasic(t *testing.T) { + type fields struct { + From string + Name string + Identity string + Website string + Description string } - tests := []struct { - name string - m *MsgRegisterRequest - want error + name string + fields fields + wantErr bool }{ - {"from nil", NewMsgRegisterRequest(nil, "", "", "", ""), ErrorInvalidFieldFrom}, - {"from zero", NewMsgRegisterRequest(sdk.AccAddress{}, "", "", "", ""), ErrorInvalidFieldFrom}, - {"from empty", NewMsgRegisterRequest(sdk.AccAddress(""), "", "", "", ""), ErrorInvalidFieldFrom}, - {"name zero", NewMsgRegisterRequest(correctAddress, "", "", "", ""), ErrorInvalidFieldName}, - {"name GT64", NewMsgRegisterRequest(correctAddress, GT64, "", "", ""), ErrorInvalidFieldName}, - {"identity GT64", NewMsgRegisterRequest(correctAddress, "test-name", GT64, "", ""), ErrorInvalidFieldIdentity}, - {"website GT64", NewMsgRegisterRequest(correctAddress, "test-name", "test-identity", GT64, ""), ErrorInvalidFieldWebsite}, - {"description GT256", NewMsgRegisterRequest(correctAddress, "test-name", "test-identity", "test-website", GT256), ErrorInvalidFieldDescription}, - {"from correct", NewMsgRegisterRequest(correctAddress, "test-name", "", "", ""), nil}, - {"name correct", NewMsgRegisterRequest(correctAddress, "test-name", "", "", ""), nil}, - {"identity empty", NewMsgRegisterRequest(correctAddress, "test-name", "", "", ""), nil}, - {"identity correct", NewMsgRegisterRequest(correctAddress, "test-name", "test-identity", "", ""), nil}, - {"website empty", NewMsgRegisterRequest(correctAddress, "test-name", "test-identity", "", ""), nil}, - {"website correct", NewMsgRegisterRequest(correctAddress, "test-name", "test-identity", "test-website", ""), nil}, - {"description empty", NewMsgRegisterRequest(correctAddress, "test-name", "test-identity", "", ""), nil}, - {"description correct", NewMsgRegisterRequest(correctAddress, "test-name", "test-identity", "", "test-description"), nil}, + { + "empty from", + fields{ + From: "", + }, + true, + }, + { + "invalid from", + fields{ + From: "invalid", + }, + true, + }, + { + "invalid prefix from", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + }, + true, + }, + { + "10 bytes from", + fields{ + From: "sent1qypqxpq9qcrsszgslawd5s", + }, + true, + }, + { + "20 bytes from", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + }, + true, + }, + { + "30 bytes from", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfqyy3zxfp9ycnjs2fszvfck8", + }, + true, + }, + { + "empty name", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Name: "", + }, + true, + }, + { + "non-empty name", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Name: "name", + }, + false, + }, + { + "length 72 name", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Name: strings.Repeat("n", 72), + }, + true, + }, + { + "empty identity", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Name: "name", + Identity: "", + }, + false, + }, + { + "non-empty identity", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Name: "name", + Identity: "identity", + }, + false, + }, + { + "length 72 identity", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Name: "name", + Identity: strings.Repeat("i", 72), + }, + true, + }, + { + "empty website", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Name: "name", + Identity: "identity", + Website: "", + }, + false, + }, + { + "non-empty website", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Name: "name", + Identity: "identity", + Website: "https://website", + }, + false, + }, + { + "length 72 website", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Name: "name", + Identity: "identity", + Website: strings.Repeat("w", 72), + }, + true, + }, + { + "invalid website", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Name: "name", + Identity: "identity", + Website: "invalid", + }, + true, + }, + { + "empty description", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Name: "name", + Identity: "identity", + Website: "https://website", + Description: "", + }, + false, + }, + { + "non-empty description", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Name: "name", + Identity: "identity", + Website: "https://website", + Description: "description", + }, + false, + }, + { + "length 264 description", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Name: "name", + Identity: "identity", + Website: "https://website", + Description: strings.Repeat("d", 264), + }, + true, + }, } - for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := tt.m.ValidateBasic(); !errors.Is(tt.want, err) { - t.Errorf("ValidateBasic() = %v, want %v", err, tt.want) + m := &MsgRegisterRequest{ + From: tt.fields.From, + Name: tt.fields.Name, + Identity: tt.fields.Identity, + Website: tt.fields.Website, + Description: tt.fields.Description, + } + if err := m.ValidateBasic(); (err != nil) != tt.wantErr { + t.Errorf("ValidateBasic() error = %v, wantErr %v", err, tt.wantErr) } }) } } -func TestMsgUpdate_ValidateBasic(t *testing.T) { - correctAddress, err := sdk.AccAddressFromBech32("sent1grdunxx5jxd0ja75wt508sn6v39p70hhw53zs8") - if err != nil { - t.Errorf("failed: %s\n", err) +func TestMsgUpdateRequest_ValidateBasic(t *testing.T) { + type fields struct { + From string + Name string + Identity string + Website string + Description string } - tests := []struct { - name string - m *MsgRegisterRequest - want error + name string + fields fields + wantErr bool }{ - {"from nil", NewMsgRegisterRequest(nil, "", "", "", ""), ErrorInvalidFieldFrom}, - {"from zero", NewMsgRegisterRequest(sdk.AccAddress{}, "", "", "", ""), ErrorInvalidFieldFrom}, - {"from empty", NewMsgRegisterRequest(sdk.AccAddress(""), "", "", "", ""), ErrorInvalidFieldFrom}, - {"name zero", NewMsgRegisterRequest(correctAddress, "", "", "", ""), ErrorInvalidFieldName}, - {"name GT64", NewMsgRegisterRequest(correctAddress, GT64, "", "", ""), ErrorInvalidFieldName}, - {"identity GT64", NewMsgRegisterRequest(correctAddress, "test-name", GT64, "", ""), ErrorInvalidFieldIdentity}, - {"website GT64", NewMsgRegisterRequest(correctAddress, "test-name", "test-identity", GT64, ""), ErrorInvalidFieldWebsite}, - {"description GT256", NewMsgRegisterRequest(correctAddress, "test-name", "test-identity", "test-website", GT256), ErrorInvalidFieldDescription}, - {"from correct", NewMsgRegisterRequest(correctAddress, "test-name", "", "", ""), nil}, - {"name correct", NewMsgRegisterRequest(correctAddress, "test-name", "", "", ""), nil}, - {"identity empty", NewMsgRegisterRequest(correctAddress, "test-name", "", "", ""), nil}, - {"identity correct", NewMsgRegisterRequest(correctAddress, "test-name", "test-identity", "", ""), nil}, - {"website empty", NewMsgRegisterRequest(correctAddress, "test-name", "test-identity", "", ""), nil}, - {"website correct", NewMsgRegisterRequest(correctAddress, "test-name", "test-identity", "test-website", ""), nil}, - {"description empty", NewMsgRegisterRequest(correctAddress, "test-name", "test-identity", "", ""), nil}, - {"description correct", NewMsgRegisterRequest(correctAddress, "test-name", "test-identity", "", "test-description"), nil}, + { + "empty address", + fields{ + From: "", + }, + true, + }, + { + "invalid address", + fields{ + From: "invalid", + }, + true, + }, + { + "invalid prefix address", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + }, + true, + }, + { + "10 bytes address", + fields{ + From: "sentprov1qypqxpq9qcrsszgsutj8xr", + }, + true, + }, + { + "20 bytes address", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + }, + false, + }, + { + "30 bytes address", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfqyy3zxfp9ycnjs2fsh33zgx", + }, + true, + }, + { + "empty name", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Name: "", + }, + false, + }, + { + "non-empty name", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Name: "name", + }, + false, + }, + { + "length 72 name", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Name: strings.Repeat("n", 72), + }, + true, + }, + { + "empty identity", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Name: "name", + Identity: "", + }, + false, + }, + { + "non-empty identity", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Name: "name", + Identity: "identity", + }, + false, + }, + { + "length 72 identity", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Name: "name", + Identity: strings.Repeat("i", 72), + }, + true, + }, + { + "empty website", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Name: "name", + Identity: "identity", + Website: "", + }, + false, + }, + { + "non-empty website", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Name: "name", + Identity: "identity", + Website: "https://website", + }, + false, + }, + { + "length 72 website", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Name: "name", + Identity: "identity", + Website: strings.Repeat("w", 72), + }, + true, + }, + { + "invalid website", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Name: "name", + Identity: "identity", + Website: "invalid", + }, + true, + }, + { + "empty description", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Name: "name", + Identity: "identity", + Website: "https://website", + Description: "", + }, + false, + }, + { + "non-empty description", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Name: "name", + Identity: "identity", + Website: "https://website", + Description: "description", + }, + false, + }, + { + "length 264 description", + fields{ + From: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Name: "name", + Identity: "identity", + Website: "https://website", + Description: strings.Repeat("d", 264), + }, + true, + }, } - for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := tt.m.ValidateBasic(); !errors.Is(tt.want, err) { - t.Errorf("ValidateBasic() = %v, want %v", err, tt.want) + m := MsgUpdateRequest{ + From: tt.fields.From, + Name: tt.fields.Name, + Identity: tt.fields.Identity, + Website: tt.fields.Website, + Description: tt.fields.Description, + } + if err := m.ValidateBasic(); (err != nil) != tt.wantErr { + t.Errorf("ValidateBasic() error = %v, wantErr %v", err, tt.wantErr) } }) } diff --git a/x/provider/types/params.go b/x/provider/types/params.go index fd723dfd..b7c384c4 100644 --- a/x/provider/types/params.go +++ b/x/provider/types/params.go @@ -19,20 +19,35 @@ var ( _ params.ParamSet = (*Params)(nil) ) -func (p *Params) Validate() error { - if !p.Deposit.IsValid() { - return fmt.Errorf("deposit should be valid") +func (m *Params) Validate() error { + if m.Deposit.IsNegative() { + return fmt.Errorf("deposit cannot be negative") + } + if !m.Deposit.IsValid() { + return fmt.Errorf("invalid deposit %s", m.Deposit) } return nil } -func (p *Params) ParamSetPairs() params.ParamSetPairs { +func (m *Params) ParamSetPairs() params.ParamSetPairs { return params.ParamSetPairs{ { Key: KeyDeposit, - Value: &p.Deposit, - ValidatorFn: func(_ interface{}) error { + Value: &m.Deposit, + ValidatorFn: func(v interface{}) error { + value, ok := v.(sdk.Coin) + if !ok { + return fmt.Errorf("invalid parameter type %T", v) + } + + if value.IsNegative() { + return fmt.Errorf("deposit cannot be negative") + } + if !value.IsValid() { + return fmt.Errorf("invalid deposit %s", value) + } + return nil }, }, diff --git a/x/provider/types/params_test.go b/x/provider/types/params_test.go new file mode 100644 index 00000000..0d9df597 --- /dev/null +++ b/x/provider/types/params_test.go @@ -0,0 +1,64 @@ +package types + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func TestParams_Validate(t *testing.T) { + type fields struct { + Deposit sdk.Coin + } + tests := []struct { + name string + fields fields + wantErr bool + }{ + { + "empty denom deposit", + fields{ + Deposit: sdk.Coin{Denom: "", Amount: sdk.NewInt(1000)}, + }, + true, + }, + { + "invalid denom deposit", + fields{ + Deposit: sdk.Coin{Denom: "0", Amount: sdk.NewInt(1000)}, + }, + true, + }, + { + "negative amount deposit", + fields{ + Deposit: sdk.Coin{Denom: "one", Amount: sdk.NewInt(-1000)}, + }, + true, + }, + { + "zero amount deposit", + fields{ + Deposit: sdk.Coin{Denom: "one", Amount: sdk.NewInt(0)}, + }, + false, + }, + { + "positive amount deposit", + fields{ + Deposit: sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}, + }, + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := &Params{ + Deposit: tt.fields.Deposit, + } + if err := p.Validate(); (err != nil) != tt.wantErr { + t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/x/provider/types/provider.go b/x/provider/types/provider.go index 820d6221..393f720e 100644 --- a/x/provider/types/provider.go +++ b/x/provider/types/provider.go @@ -2,16 +2,19 @@ package types import ( "fmt" + "net/url" + + "github.com/cosmos/cosmos-sdk/types/errors" hubtypes "github.com/sentinel-official/hub/types" ) -func (p *Provider) GetAddress() hubtypes.ProvAddress { - if p.Address == "" { +func (m *Provider) GetAddress() hubtypes.ProvAddress { + if m.Address == "" { return nil } - address, err := hubtypes.ProvAddressFromBech32(p.Address) + address, err := hubtypes.ProvAddressFromBech32(m.Address) if err != nil { panic(err) } @@ -19,24 +22,37 @@ func (p *Provider) GetAddress() hubtypes.ProvAddress { return address } -func (p *Provider) Validate() error { - if _, err := hubtypes.ProvAddressFromBech32(p.Address); err != nil { - return fmt.Errorf("address should not be nil or empty") +func (m *Provider) Validate() error { + if m.Address == "" { + return fmt.Errorf("address cannot be empty") + } + if _, err := hubtypes.ProvAddressFromBech32(m.Address); err != nil { + return errors.Wrapf(err, "invalid address %s", m.Address) } - if len(p.Name) == 0 || len(p.Name) > 64 { - return fmt.Errorf("name length should be between 1 and 64") + if m.Name == "" { + return fmt.Errorf("name cannot be empty") } - if len(p.Identity) > 64 { - return fmt.Errorf("identity length should be between 0 and 64") + if len(m.Name) > 64 { + return fmt.Errorf("name length cannot be greater than %d", 64) } - if len(p.Website) > 64 { - return fmt.Errorf("website length should be between 0 and 64") + if len(m.Identity) > 64 { + return fmt.Errorf("identity length cannot be greater than %d", 64) } - if len(p.Description) > 256 { - return fmt.Errorf("description length should be between 0 and 256") + if m.Website != "" { + if len(m.Website) > 64 { + return fmt.Errorf("website length cannot be greater than %d", 64) + } + if _, err := url.ParseRequestURI(m.Website); err != nil { + return errors.Wrapf(err, "invalid website %s", m.Website) + } + } + if len(m.Description) > 256 { + return fmt.Errorf("description length cannot be greater than %d", 256) } return nil } -type Providers []Provider +type ( + Providers []Provider +) diff --git a/x/provider/types/provider_test.go b/x/provider/types/provider_test.go index 99e3df5e..f3feda0d 100644 --- a/x/provider/types/provider_test.go +++ b/x/provider/types/provider_test.go @@ -1,33 +1,248 @@ package types import ( + "reflect" + "strings" "testing" hubtypes "github.com/sentinel-official/hub/types" ) -func TestProvider_Validate(t *testing.T) { - correctAddress := hubtypes.ProvAddress{} +func TestProvider_GetAddress(t *testing.T) { + type fields struct { + Address string + Name string + Identity string + Website string + Description string + } + tests := []struct { + name string + fields fields + want hubtypes.ProvAddress + }{ + { + "empty", + fields{ + Address: "", + }, + nil, + }, + { + "20 bytes", + fields{ + Address: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + }, + hubtypes.ProvAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := &Provider{ + Address: tt.fields.Address, + Name: tt.fields.Name, + Identity: tt.fields.Identity, + Website: tt.fields.Website, + Description: tt.fields.Description, + } + if got := p.GetAddress(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("GetAddress() = %v, want %v", got, tt.want) + } + }) + } +} - // we dont need to check for contents of the error message, checking whether the error is nil/not-nil is enough. +func TestProvider_Validate(t *testing.T) { + type fields struct { + Address string + Name string + Identity string + Website string + Description string + } tests := []struct { - name string - p Provider + name string + fields fields + wantErr bool }{ - {"address zero", Provider{"", "", "", "", ""}}, - {"address empty", Provider{correctAddress.String(), "", "", "", ""}}, - {"name length zero", Provider{correctAddress.String(), "", "", "", ""}}, - {"name length greater than 64", Provider{correctAddress.String(), GT64, "", "", ""}}, - {"identity length greater than 64", Provider{correctAddress.String(), "name", GT64, "", ""}}, - {"website length greater than 64", Provider{correctAddress.String(), "name", "", GT64, ""}}, - {"description length greater than 256", Provider{correctAddress.String(), "name", "", "", GT256}}, - {"valid", Provider{correctAddress.String(), "name", "", "", ""}}, + { + "empty address", + fields{ + Address: "", + }, + true, + }, + { + "invalid address", + fields{ + Address: "invalid", + }, + true, + }, + { + "invalid prefix address", + fields{ + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + }, + true, + }, + { + "10 bytes address", + fields{ + Address: "sentprov1qypqxpq9qcrsszgsutj8xr", + }, + true, + }, + { + "20 bytes address", + fields{ + Address: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + }, + true, + }, + { + "30 bytes address", + fields{ + Address: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfqyy3zxfp9ycnjs2fsh33zgx", + }, + true, + }, + { + "empty name", + fields{ + Address: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Name: "", + }, + true, + }, + { + "non-empty name", + fields{ + Address: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Name: "name", + }, + false, + }, + { + "length 72 name", + fields{ + Address: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Name: strings.Repeat("n", 72), + }, + true, + }, + { + "empty identity", + fields{ + Address: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Name: "name", + Identity: "", + }, + false, + }, + { + "non-empty identity", + fields{ + Address: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Name: "name", + Identity: "identity", + }, + false, + }, + { + "length 72 identity", + fields{ + Address: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Name: "name", + Identity: strings.Repeat("i", 72), + }, + true, + }, + { + "empty website", + fields{ + Address: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Name: "name", + Identity: "identity", + Website: "", + }, + false, + }, + { + "non-empty website", + fields{ + Address: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Name: "name", + Identity: "identity", + Website: "https://website", + }, + false, + }, + { + "length 72 website", + fields{ + Address: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Name: "name", + Identity: "identity", + Website: strings.Repeat("w", 72), + }, + true, + }, + { + "invalid website", + fields{ + Address: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Name: "name", + Identity: "identity", + Website: "invalid", + }, + true, + }, + { + "empty description", + fields{ + Address: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Name: "name", + Identity: "identity", + Website: "https://website", + Description: "", + }, + false, + }, + { + "non-empty description", + fields{ + Address: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Name: "name", + Identity: "identity", + Website: "https://website", + Description: "description", + }, + false, + }, + { + "length 264 description", + fields{ + Address: "sentprov1qypqxpq9qcrsszgszyfpx9q4zct3sxfq877k82", + Name: "name", + Identity: "identity", + Website: "https://website", + Description: strings.Repeat("d", 264), + }, + true, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := tt.p.Validate() - if err == nil && tt.name != "valid" { - t.Errorf("Validate() error = %v", err) + p := &Provider{ + Address: tt.fields.Address, + Name: tt.fields.Name, + Identity: tt.fields.Identity, + Website: tt.fields.Website, + Description: tt.fields.Description, + } + if err := p.Validate(); (err != nil) != tt.wantErr { + t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr) } }) } diff --git a/x/session/expected/keeper.go b/x/session/expected/keeper.go index 84fe352f..06d30724 100644 --- a/x/session/expected/keeper.go +++ b/x/session/expected/keeper.go @@ -13,13 +13,16 @@ type AccountKeeper interface { GetAccount(ctx sdk.Context, address sdk.AccAddress) authtypes.AccountI } +type BankKeeper interface { + SpendableCoins(ctx sdk.Context, address sdk.AccAddress) sdk.Coins +} + type DepositKeeper interface { SendCoinsFromDepositToAccount(ctx sdk.Context, from, to sdk.AccAddress, coins sdk.Coins) error } type PlanKeeper interface { HasNodeForPlan(ctx sdk.Context, id uint64, address hubtypes.NodeAddress) bool - GetNodesForPlan(ctx sdk.Context, id uint64, skip, limit int64) nodetypes.Nodes } type NodeKeeper interface { @@ -27,14 +30,11 @@ type NodeKeeper interface { } type SubscriptionKeeper interface { - GetSubscription(ctx sdk.Context, id uint64) (subscriptiontypes.Subscription, bool) - - GetSubscriptionsForNode(ctx sdk.Context, address hubtypes.NodeAddress, skip, limit int64) subscriptiontypes.Subscriptions HasSubscriptionForNode(ctx sdk.Context, address hubtypes.NodeAddress, id uint64) bool - + GetSubscription(ctx sdk.Context, id uint64) (subscriptiontypes.Subscription, bool) GetActiveSubscriptionsForAddress(ctx sdk.Context, address sdk.AccAddress, skip, limit int64) subscriptiontypes.Subscriptions SetQuota(ctx sdk.Context, id uint64, quota subscriptiontypes.Quota) - GetQuota(ctx sdk.Context, id uint64, address sdk.AccAddress) (subscriptiontypes.Quota, bool) HasQuota(ctx sdk.Context, id uint64, address sdk.AccAddress) bool + GetQuota(ctx sdk.Context, id uint64, address sdk.AccAddress) (subscriptiontypes.Quota, bool) } diff --git a/x/session/keeper/alias.go b/x/session/keeper/alias.go index a9d6875f..59ac86e0 100644 --- a/x/session/keeper/alias.go +++ b/x/session/keeper/alias.go @@ -14,6 +14,14 @@ func (k *Keeper) GetAccount(ctx sdk.Context, address sdk.AccAddress) authtypes.A return k.account.GetAccount(ctx, address) } +func (k *Keeper) SendCoinsFromDepositToAccount(ctx sdk.Context, from, to sdk.AccAddress, coin sdk.Coin) error { + if coin.IsZero() { + return nil + } + + return k.deposit.SendCoinsFromDepositToAccount(ctx, from, to, sdk.NewCoins(coin)) +} + func (k *Keeper) HasNodeForPlan(ctx sdk.Context, id uint64, address hubtypes.NodeAddress) bool { return k.plan.HasNodeForPlan(ctx, id, address) } @@ -22,30 +30,26 @@ func (k *Keeper) GetNode(ctx sdk.Context, address hubtypes.NodeAddress) (nodetyp return k.node.GetNode(ctx, address) } +func (k *Keeper) HasSubscriptionForNode(ctx sdk.Context, address hubtypes.NodeAddress, id uint64) bool { + return k.subscription.HasSubscriptionForNode(ctx, address, id) +} + func (k *Keeper) GetSubscription(ctx sdk.Context, id uint64) (subscriptiontypes.Subscription, bool) { return k.subscription.GetSubscription(ctx, id) } -func (k *Keeper) HasSubscriptionForNode(ctx sdk.Context, address hubtypes.NodeAddress, id uint64) bool { - return k.subscription.HasSubscriptionForNode(ctx, address, id) +func (k *Keeper) GetActiveSubscriptionsForAddress(ctx sdk.Context, address sdk.AccAddress, skip, limit int64) subscriptiontypes.Subscriptions { + return k.subscription.GetActiveSubscriptionsForAddress(ctx, address, skip, limit) } func (k *Keeper) SetQuota(ctx sdk.Context, id uint64, quota subscriptiontypes.Quota) { k.subscription.SetQuota(ctx, id, quota) } -func (k *Keeper) GetQuota(ctx sdk.Context, id uint64, address sdk.AccAddress) (subscriptiontypes.Quota, bool) { - return k.subscription.GetQuota(ctx, id, address) -} - func (k *Keeper) HasQuota(ctx sdk.Context, id uint64, address sdk.AccAddress) bool { return k.subscription.HasQuota(ctx, id, address) } -func (k *Keeper) SendCoinsFromDepositToAccount(ctx sdk.Context, from, to sdk.AccAddress, coin sdk.Coin) error { - if coin.IsZero() { - return nil - } - - return k.deposit.SendCoinsFromDepositToAccount(ctx, from, to, sdk.NewCoins(coin)) +func (k *Keeper) GetQuota(ctx sdk.Context, id uint64, address sdk.AccAddress) (subscriptiontypes.Quota, bool) { + return k.subscription.GetQuota(ctx, id, address) } diff --git a/x/session/keeper/proof.go b/x/session/keeper/proof.go index 26e5a12d..e19af6a4 100644 --- a/x/session/keeper/proof.go +++ b/x/session/keeper/proof.go @@ -1,6 +1,8 @@ package keeper import ( + "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/sentinel-official/hub/x/session/types" @@ -9,10 +11,10 @@ import ( func (k *Keeper) VerifyProof(ctx sdk.Context, address sdk.AccAddress, proof types.Proof, signature []byte) error { account := k.GetAccount(ctx, address) if account == nil { - return types.ErrorAccountDoesNotExist + return fmt.Errorf("account for address %s does not exist", address) } if account.GetPubKey() == nil { - return types.ErrorPublicKeyDoesNotExist + return fmt.Errorf("public key for address %s does not exist", address) } bytes, err := proof.Marshal() @@ -21,7 +23,7 @@ func (k *Keeper) VerifyProof(ctx sdk.Context, address sdk.AccAddress, proof type } if !account.GetPubKey().VerifySignature(bytes, signature) { - return types.ErrorInvalidSignature + return fmt.Errorf("either message or signature is invalid") } return nil diff --git a/x/session/simulation/decoder.go b/x/session/simulation/decoder.go new file mode 100644 index 00000000..31f541f2 --- /dev/null +++ b/x/session/simulation/decoder.go @@ -0,0 +1,63 @@ +package simulation + +import ( + "bytes" + "fmt" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/types/kv" + protobuftypes "github.com/gogo/protobuf/types" + + "github.com/sentinel-official/hub/x/session/types" +) + +func NewStoreDecoder(cdc codec.Marshaler) func(kvA, kvB kv.Pair) string { + return func(kvA, kvB kv.Pair) string { + switch { + case bytes.Equal(kvA.Key[:1], types.CountKey): + var countA, countB protobuftypes.UInt64Value + cdc.MustUnmarshalBinaryBare(kvA.Value, &countA) + cdc.MustUnmarshalBinaryBare(kvB.Value, &countB) + + return fmt.Sprintf("%v\n%v", &countA, &countB) + case bytes.Equal(kvA.Key[:1], types.SessionKeyPrefix): + var sessionA, sessionB types.Session + cdc.MustUnmarshalBinaryBare(kvA.Value, &sessionA) + cdc.MustUnmarshalBinaryBare(kvB.Value, &sessionB) + + return fmt.Sprintf("%v\n%v", &sessionA, &sessionB) + case bytes.Equal(kvA.Key[:1], types.SessionForSubscriptionKeyPrefix): + var sessionForSubscriptionA, sessionForSubscriptionB protobuftypes.BoolValue + cdc.MustUnmarshalBinaryBare(kvA.Value, &sessionForSubscriptionA) + cdc.MustUnmarshalBinaryBare(kvB.Value, &sessionForSubscriptionB) + + return fmt.Sprintf("%v\n%v", &sessionForSubscriptionA, &sessionForSubscriptionB) + case bytes.Equal(kvA.Key[:1], types.SessionForNodeKeyPrefix): + var sessionForNodeA, sessionForNodeB protobuftypes.BoolValue + cdc.MustUnmarshalBinaryBare(kvA.Value, &sessionForNodeA) + cdc.MustUnmarshalBinaryBare(kvB.Value, &sessionForNodeB) + + return fmt.Sprintf("%v\n%v", &sessionForNodeA, &sessionForNodeB) + case bytes.Equal(kvA.Key[:1], types.InactiveSessionForAddressKeyPrefix): + var inactiveSessionForAddressA, inactiveSessionForAddressB protobuftypes.BoolValue + cdc.MustUnmarshalBinaryBare(kvA.Value, &inactiveSessionForAddressA) + cdc.MustUnmarshalBinaryBare(kvB.Value, &inactiveSessionForAddressB) + + return fmt.Sprintf("%v\n%v", &inactiveSessionForAddressA, &inactiveSessionForAddressB) + case bytes.Equal(kvA.Key[:1], types.ActiveSessionForAddressKeyPrefix): + var activeSessionForAddressA, activeSessionForAddressB protobuftypes.BoolValue + cdc.MustUnmarshalBinaryBare(kvA.Value, &activeSessionForAddressA) + cdc.MustUnmarshalBinaryBare(kvB.Value, &activeSessionForAddressB) + + return fmt.Sprintf("%v\n%v", &activeSessionForAddressA, &activeSessionForAddressB) + case bytes.Equal(kvA.Key[:1], types.InactiveSessionAtKeyPrefix): + var inactiveSessionAtA, inactiveSessionAtB protobuftypes.BoolValue + cdc.MustUnmarshalBinaryBare(kvA.Value, &inactiveSessionAtA) + cdc.MustUnmarshalBinaryBare(kvB.Value, &inactiveSessionAtB) + + return fmt.Sprintf("%v\n%v", &inactiveSessionAtA, &inactiveSessionAtB) + } + + panic(fmt.Sprintf("invalid key prefix %X", kvA.Key[:1])) + } +} diff --git a/x/session/simulation/genesis.go b/x/session/simulation/genesis.go new file mode 100644 index 00000000..44c63e31 --- /dev/null +++ b/x/session/simulation/genesis.go @@ -0,0 +1,31 @@ +package simulation + +import ( + "math/rand" + "time" + + "github.com/cosmos/cosmos-sdk/types/module" + + "github.com/sentinel-official/hub/x/session/types" +) + +func RandomizedGenesisState(state *module.SimulationState) *types.GenesisState { + var ( + inactiveDuration time.Duration + ) + + state.AppParams.GetOrGenerate( + state.Cdc, + string(types.KeyInactiveDuration), + &inactiveDuration, + state.Rand, + func(r *rand.Rand) { + inactiveDuration = time.Duration(r.Int63n(MaxInactiveDuration)) * time.Millisecond + }, + ) + + return types.NewGenesisState( + nil, + types.NewParams(inactiveDuration, false), + ) +} diff --git a/x/session/simulation/operations.go b/x/session/simulation/operations.go new file mode 100644 index 00000000..111b9fea --- /dev/null +++ b/x/session/simulation/operations.go @@ -0,0 +1,304 @@ +package simulation + +import ( + "math/rand" + "time" + + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/simapp/helpers" + "github.com/cosmos/cosmos-sdk/simapp/params" + sdk "github.com/cosmos/cosmos-sdk/types" + simulationtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/cosmos/cosmos-sdk/x/simulation" + + hubtypes "github.com/sentinel-official/hub/types" + "github.com/sentinel-official/hub/x/session/expected" + "github.com/sentinel-official/hub/x/session/keeper" + "github.com/sentinel-official/hub/x/session/types" +) + +var ( + OperationWeightMsgStartRequest = "op_weight_" + types.TypeMsgStartRequest + OperationWeightMsgUpdateRequest = "op_weight_" + types.TypeMsgUpdateRequest + OperationWeightMsgEndRequest = "op_weight_" + types.TypeMsgEndRequest +) + +func WeightedOperations( + params simulationtypes.AppParams, + cdc codec.JSONMarshaler, + ak expected.AccountKeeper, + bk expected.BankKeeper, + k keeper.Keeper, +) simulation.WeightedOperations { + var ( + weightMsgStartRequest int + weightMsgUpdateRequest int + weightMsgEndRequest int + ) + + params.GetOrGenerate( + cdc, + OperationWeightMsgStartRequest, + &weightMsgStartRequest, + nil, + func(_ *rand.Rand) { + weightMsgStartRequest = 100 + }, + ) + params.GetOrGenerate( + cdc, + OperationWeightMsgUpdateRequest, + &weightMsgUpdateRequest, + nil, + func(_ *rand.Rand) { + weightMsgUpdateRequest = 100 + }, + ) + params.GetOrGenerate( + cdc, + OperationWeightMsgEndRequest, + &weightMsgEndRequest, + nil, + func(_ *rand.Rand) { + weightMsgEndRequest = 100 + }, + ) + + return simulation.WeightedOperations{ + simulation.NewWeightedOperation( + weightMsgStartRequest, + SimulateMsgStartRequest(ak, bk, k), + ), + simulation.NewWeightedOperation( + weightMsgUpdateRequest, + SimulateMsgUpdateRequest(ak, bk, k), + ), + simulation.NewWeightedOperation( + weightMsgEndRequest, + SimulateMsgEndRequest(ak, bk, k), + ), + } +} + +func SimulateMsgStartRequest(ak expected.AccountKeeper, bk expected.BankKeeper, k keeper.Keeper) simulationtypes.Operation { + return func( + r *rand.Rand, + app *baseapp.BaseApp, + ctx sdk.Context, + accounts []simulationtypes.Account, + chainID string, + ) (simulationtypes.OperationMsg, []simulationtypes.FutureOperation, error) { + var ( + rFrom, _ = simulationtypes.RandomAcc(r, accounts) + from = ak.GetAccount(ctx, rFrom.Address) + rAddress, _ = simulationtypes.RandomAcc(r, accounts) + address = ak.GetAccount(ctx, rAddress.Address) + ) + + subscriptions := k.GetActiveSubscriptionsForAddress(ctx, from.GetAddress(), 0, 0) + if len(subscriptions) == 0 { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgStartRequest, "active subscriptions for address does not exist"), nil, nil + } + + var ( + rSubscription = subscriptions[r.Intn(len(subscriptions))] + ) + + node, found := k.GetNode(ctx, hubtypes.NodeAddress(address.GetAddress())) + if !found { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgStartRequest, "node does not exist"), nil, nil + } + if !node.Status.Equal(hubtypes.StatusActive) { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgStartRequest, "node status is not active"), nil, nil + } + if rSubscription.Plan != 0 { + if k.HasNodeForPlan(ctx, rSubscription.Plan, hubtypes.NodeAddress(address.String())) { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgStartRequest, "node for plan does not exist"), nil, nil + } + } + + balance := bk.SpendableCoins(ctx, from.GetAddress()) + if !balance.IsAnyNegative() { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgStartRequest, "balance is negative"), nil, nil + } + + fees, err := simulationtypes.RandomFees(r, ctx, balance) + if err != nil { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgStartRequest, err.Error()), nil, err + } + + var ( + txConfig = params.MakeTestEncodingConfig().TxConfig + message = types.NewMsgStartRequest( + from.GetAddress(), + rSubscription.Id, + hubtypes.NodeAddress(address.GetAddress()), + ) + ) + + txn, err := helpers.GenTx( + txConfig, + []sdk.Msg{message}, + fees, + helpers.DefaultGenTxGas, + chainID, + []uint64{from.GetAccountNumber()}, + []uint64{from.GetSequence()}, + rFrom.PrivKey, + ) + if err != nil { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgStartRequest, err.Error()), nil, err + } + + _, _, err = app.Deliver(txConfig.TxEncoder(), txn) + if err != nil { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgStartRequest, err.Error()), nil, err + } + + return simulationtypes.NewOperationMsg(message, true, ""), nil, nil + } +} + +func SimulateMsgUpdateRequest(ak expected.AccountKeeper, bk expected.BankKeeper, k keeper.Keeper) simulationtypes.Operation { + return func( + r *rand.Rand, + app *baseapp.BaseApp, + ctx sdk.Context, + accounts []simulationtypes.Account, + chainID string, + ) (simulationtypes.OperationMsg, []simulationtypes.FutureOperation, error) { + var ( + rFrom, _ = simulationtypes.RandomAcc(r, accounts) + from = ak.GetAccount(ctx, rFrom.Address) + rId = uint64(r.Int63n(1 << 18)) + ) + + rSession, found := k.GetSession(ctx, rId) + if !found { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgUpdateRequest, "session does not exist"), nil, nil + } + if rSession.Status.Equal(hubtypes.Inactive) { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgUpdateRequest, "session status is inactive"), nil, nil + } + if rSession.Node == hubtypes.NodeAddress(from.GetAddress()).String() { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgUpdateRequest, "session does not belong to node"), nil, nil + } + + balance := bk.SpendableCoins(ctx, from.GetAddress()) + if !balance.IsAnyNegative() { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgUpdateRequest, "balance is negative"), nil, nil + } + + fees, err := simulationtypes.RandomFees(r, ctx, balance) + if err != nil { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgUpdateRequest, err.Error()), nil, err + } + + var ( + duration = time.Duration(r.Int63n(MaxSessionDuration)) * time.Minute + bandwidth = hubtypes.Bandwidth{ + Upload: sdk.NewInt(r.Int63n(MaxSessionBandwidthUpload)), + Download: sdk.NewInt(r.Int63n(MaxSessionBandwidthDownload)), + } + ) + + var ( + txConfig = params.MakeTestEncodingConfig().TxConfig + message = types.NewMsgUpdateRequest( + hubtypes.NodeAddress(from.GetAddress()), + types.Proof{ + Id: rSession.Id, + Duration: duration, + Bandwidth: bandwidth, + }, + nil, + ) + ) + + txn, err := helpers.GenTx( + txConfig, + []sdk.Msg{message}, + fees, + helpers.DefaultGenTxGas, + chainID, + []uint64{from.GetAccountNumber()}, + []uint64{from.GetSequence()}, + rFrom.PrivKey, + ) + if err != nil { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgUpdateRequest, err.Error()), nil, err + } + + _, _, err = app.Deliver(txConfig.TxEncoder(), txn) + if err != nil { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgUpdateRequest, err.Error()), nil, err + } + + return simulationtypes.NewOperationMsg(message, true, ""), nil, nil + } +} + +func SimulateMsgEndRequest(ak expected.AccountKeeper, bk expected.BankKeeper, k keeper.Keeper) simulationtypes.Operation { + return func( + r *rand.Rand, + app *baseapp.BaseApp, + ctx sdk.Context, + accounts []simulationtypes.Account, + chainID string, + ) (simulationtypes.OperationMsg, []simulationtypes.FutureOperation, error) { + var ( + rFrom, _ = simulationtypes.RandomAcc(r, accounts) + from = ak.GetAccount(ctx, rFrom.Address) + ) + + sessions := k.GetActiveSessionsForAddress(ctx, from.GetAddress(), 0, 0) + if len(sessions) == 0 { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgEndRequest, "sessions for address does not exist"), nil, nil + } + + var ( + rSession = sessions[r.Intn(len(sessions))] + ) + + balance := bk.SpendableCoins(ctx, from.GetAddress()) + if !balance.IsAnyNegative() { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgEndRequest, "balance is negative"), nil, nil + } + + fees, err := simulationtypes.RandomFees(r, ctx, balance) + if err != nil { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgEndRequest, err.Error()), nil, err + } + + var ( + txConfig = params.MakeTestEncodingConfig().TxConfig + message = types.NewMsgEndRequest( + from.GetAddress(), + rSession.Id, + 0, + ) + ) + + txn, err := helpers.GenTx( + txConfig, + []sdk.Msg{message}, + fees, + helpers.DefaultGenTxGas, + chainID, + []uint64{from.GetAccountNumber()}, + []uint64{from.GetSequence()}, + rFrom.PrivKey, + ) + if err != nil { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgEndRequest, err.Error()), nil, err + } + + _, _, err = app.Deliver(txConfig.TxEncoder(), txn) + if err != nil { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgEndRequest, err.Error()), nil, err + } + + return simulationtypes.NewOperationMsg(message, true, ""), nil, nil + } +} diff --git a/x/session/simulation/params.go b/x/session/simulation/params.go new file mode 100644 index 00000000..3c666e7a --- /dev/null +++ b/x/session/simulation/params.go @@ -0,0 +1,38 @@ +package simulation + +import ( + "fmt" + "math/rand" + "time" + + simulationtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/cosmos/cosmos-sdk/x/simulation" + + "github.com/sentinel-official/hub/x/session/types" +) + +const ( + MaxInactiveDuration = 1 << 18 +) + +func ParamChanges(_ *rand.Rand) []simulationtypes.ParamChange { + return []simulationtypes.ParamChange{ + simulation.NewSimParamChange( + types.ModuleName, + string(types.KeyProofVerificationEnabled), + func(r *rand.Rand) string { + return fmt.Sprintf("%v", false) + }, + ), + simulation.NewSimParamChange( + types.ModuleName, + string(types.KeyInactiveDuration), + func(r *rand.Rand) string { + return fmt.Sprintf( + "%s", + time.Duration(r.Int63n(MaxInactiveDuration))*time.Millisecond, + ) + }, + ), + } +} diff --git a/x/session/simulation/rand.go b/x/session/simulation/rand.go new file mode 100644 index 00000000..d5dbea68 --- /dev/null +++ b/x/session/simulation/rand.go @@ -0,0 +1,11 @@ +package simulation + +import ( + "math" +) + +const ( + MaxSessionDuration = 1 << 18 + MaxSessionBandwidthUpload = math.MaxInt32 + MaxSessionBandwidthDownload = math.MaxInt32 +) diff --git a/x/session/types/errors.go b/x/session/types/errors.go index db41ba80..9465d51f 100644 --- a/x/session/types/errors.go +++ b/x/session/types/errors.go @@ -5,21 +5,29 @@ import ( ) var ( - ErrorInvalidField = errors.Register(ModuleName, 101, "invalid field") - ErrorSubscriptionDoesNotExit = errors.Register(ModuleName, 102, "subscription does not exist") - ErrorInvalidSubscriptionStatus = errors.Register(ModuleName, 103, "invalid subscription status") - ErrorUnauthorized = errors.Register(ModuleName, 104, "unauthorized") - ErrorQuotaDoesNotExist = errors.Register(ModuleName, 105, "quota does not exist") - ErrorFailedToVerifyProof = errors.Register(ModuleName, 106, "failed to verify proof") - ErrorNotEnoughQuota = errors.Register(ModuleName, 107, "not enough quota") - ErrorNodeDoesNotExist = errors.Register(ModuleName, 108, "node does not exist") - ErrorInvalidNodeStatus = errors.Register(ModuleName, 109, "invalid node status") - ErrorSessionDoesNotExist = errors.Register(ModuleName, 110, "session does not exist") - ErrorInvalidSessionStatus = errors.Register(ModuleName, 111, "invalid session status") - ErrorAccountDoesNotExist = errors.Register(ModuleName, 112, "account does not exist") - ErrorPublicKeyDoesNotExist = errors.Register(ModuleName, 113, "public key does not exist") - ErrorInvalidSignature = errors.Register(ModuleName, 114, "invalid signature") - ErrorNodeAddressMismatch = errors.Register(ModuleName, 115, "node address mismatch") - ErrorNodeDoesNotExistForPlan = errors.Register(ModuleName, 116, "node does not exist for plan") - ErrorDuplicateSession = errors.Register(ModuleName, 117, "duplicate session") + ErrorInvalidField = errors.Register(ModuleName, 101, "invalid field") + ErrorInvalidFrom = errors.Register(ModuleName, 102, "invalid from") + ErrorInvalidId = errors.Register(ModuleName, 103, "invalid id") + ErrorInvalidNode = errors.Register(ModuleName, 104, "invalid node") + ErrorInvalidProofId = errors.Register(ModuleName, 105, "invalid proof->id") + ErrorInvalidProofDuration = errors.Register(ModuleName, 106, "invalid proof->duration") + ErrorInvalidProofBandwidth = errors.Register(ModuleName, 107, "invalid proof->bandwidth") + ErrorInvalidSignature = errors.Register(ModuleName, 108, "invalid signature") + ErrorInvalidRating = errors.Register(ModuleName, 109, "invalid rating") +) + +var ( + ErrorSubscriptionDoesNotExit = errors.Register(ModuleName, 201, "subscription does not exist") + ErrorInvalidSubscriptionStatus = errors.Register(ModuleName, 202, "invalid subscription status") + ErrorUnauthorized = errors.Register(ModuleName, 203, "unauthorized") + ErrorQuotaDoesNotExist = errors.Register(ModuleName, 204, "quota does not exist") + ErrorFailedToVerifyProof = errors.Register(ModuleName, 205, "failed to verify proof") + ErrorNotEnoughQuota = errors.Register(ModuleName, 206, "not enough quota") + ErrorNodeDoesNotExist = errors.Register(ModuleName, 207, "node does not exist") + ErrorInvalidNodeStatus = errors.Register(ModuleName, 208, "invalid node status") + ErrorSessionDoesNotExist = errors.Register(ModuleName, 209, "session does not exist") + ErrorInvalidSessionStatus = errors.Register(ModuleName, 210, "invalid session status") + ErrorNodeAddressMismatch = errors.Register(ModuleName, 211, "node address mismatch") + ErrorNodeDoesNotExistForPlan = errors.Register(ModuleName, 212, "node does not exist for plan") + ErrorDuplicateSession = errors.Register(ModuleName, 213, "duplicate session") ) diff --git a/x/session/types/genesis.go b/x/session/types/genesis.go index 10f17b69..63953bab 100644 --- a/x/session/types/genesis.go +++ b/x/session/types/genesis.go @@ -20,20 +20,20 @@ func ValidateGenesis(state *GenesisState) error { return err } - for _, session := range state.Sessions { - if err := session.Validate(); err != nil { - return err - } - } - sessions := make(map[uint64]bool) for _, item := range state.Sessions { if sessions[item.Id] { - return fmt.Errorf("duplicate session id %d", item.Id) + return fmt.Errorf("found duplicate session for id %d", item.Id) } sessions[item.Id] = true } + for _, session := range state.Sessions { + if err := session.Validate(); err != nil { + return err + } + } + return nil } diff --git a/x/session/types/genesis_test.go b/x/session/types/genesis_test.go new file mode 100644 index 00000000..73e446b0 --- /dev/null +++ b/x/session/types/genesis_test.go @@ -0,0 +1,61 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestDefaultGenesisState(t *testing.T) { + var ( + state *GenesisState + ) + + state = DefaultGenesisState() + require.Equal(t, &GenesisState{ + Sessions: nil, + Params: DefaultParams(), + }, state) +} + +func TestNewGenesisState(t *testing.T) { + var ( + subscriptions Sessions + params Params + state *GenesisState + ) + + state = NewGenesisState(nil, params) + require.Equal(t, &GenesisState{ + Sessions: nil, + Params: params, + }, state) + require.Len(t, state.Sessions, 0) + + state = NewGenesisState(subscriptions, params) + require.Equal(t, &GenesisState{ + Sessions: subscriptions, + Params: params, + }, state) + require.Len(t, state.Sessions, 0) + + subscriptions = append(subscriptions, + Session{}, + ) + state = NewGenesisState(subscriptions, params) + require.Equal(t, &GenesisState{ + Sessions: subscriptions, + Params: params, + }, state) + require.Len(t, state.Sessions, 1) + + subscriptions = append(subscriptions, + Session{}, + ) + state = NewGenesisState(subscriptions, params) + require.Equal(t, &GenesisState{ + Sessions: subscriptions, + Params: params, + }, state) + require.Len(t, state.Sessions, 2) +} diff --git a/x/session/types/keys.go b/x/session/types/keys.go index de6f46f2..98932598 100644 --- a/x/session/types/keys.go +++ b/x/session/types/keys.go @@ -19,6 +19,12 @@ var ( StoreKey = ModuleName ) +var ( + TypeMsgStartRequest = ModuleName + ":start" + TypeMsgUpdateRequest = ModuleName + ":update" + TypeMsgEndRequest = ModuleName + ":end" +) + var ( EventModuleName = EventModule{Name: ModuleName} ) diff --git a/x/session/types/msg.go b/x/session/types/msg.go index ccb8f2a7..01eed4aa 100644 --- a/x/session/types/msg.go +++ b/x/session/types/msg.go @@ -1,8 +1,6 @@ package types import ( - "fmt" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/errors" @@ -28,23 +26,24 @@ func (m *MsgStartRequest) Route() string { } func (m *MsgStartRequest) Type() string { - return fmt.Sprintf("%s:start", ModuleName) + return TypeMsgStartRequest } func (m *MsgStartRequest) ValidateBasic() error { - // From shouldn't be nil or empty + if m.From == "" { + return errors.Wrap(ErrorInvalidFrom, "from cannot be empty") + } if _, err := sdk.AccAddressFromBech32(m.From); err != nil { - return errors.Wrapf(ErrorInvalidField, "%s", "from") + return errors.Wrapf(ErrorInvalidFrom, "%s", err) } - - // Id should be positive if m.Id == 0 { - return errors.Wrapf(ErrorInvalidField, "%s", "id") + return errors.Wrap(ErrorInvalidId, "id cannot be zero") + } + if m.Node == "" { + return errors.Wrap(ErrorInvalidNode, "node cannot be empty") } - - // Address shouldn't be nil or empty if _, err := hubtypes.NodeAddressFromBech32(m.Node); err != nil { - return errors.Wrapf(ErrorInvalidField, "%s", "address") + return errors.Wrapf(ErrorInvalidNode, "%s", err) } return nil @@ -76,33 +75,32 @@ func (m *MsgUpdateRequest) Route() string { } func (m *MsgUpdateRequest) Type() string { - return fmt.Sprintf("%s:update", ModuleName) + return TypeMsgUpdateRequest } func (m *MsgUpdateRequest) ValidateBasic() error { - // From shouldn't be nil or empty + if m.From == "" { + return errors.Wrap(ErrorInvalidFrom, "from cannot be empty") + } if _, err := hubtypes.NodeAddressFromBech32(m.From); err != nil { - return errors.Wrapf(ErrorInvalidField, "%s", "from") + return errors.Wrapf(ErrorInvalidFrom, "%s", err) } - - // Id should be positive if m.Proof.Id == 0 { - return errors.Wrapf(ErrorInvalidField, "%s", "proof->id") + return errors.Wrap(ErrorInvalidProofId, "proof->id cannot be zero") } - - // Duration shouldn't be negative if m.Proof.Duration < 0 { - return errors.Wrapf(ErrorInvalidField, "%s", "proof->duration") + return errors.Wrap(ErrorInvalidProofDuration, "proof->duration cannot be negative") } - - // Bandwidth shouldn't be negative if m.Proof.Bandwidth.IsAnyNegative() { - return errors.Wrapf(ErrorInvalidField, "%s", "proof->bandwidth") + return errors.Wrap(ErrorInvalidProofBandwidth, "proof->bandwidth cannot be negative") } - - // Signature can be nil, if not length should be 64 - if m.Signature != nil && len(m.Signature) != 64 { - return errors.Wrapf(ErrorInvalidField, "%s", "signature") + if m.Signature != nil { + if len(m.Signature) < 64 { + return errors.Wrapf(ErrorInvalidSignature, "signature length cannot be less than %d", 64) + } + if len(m.Signature) > 64 { + return errors.Wrapf(ErrorInvalidSignature, "signature length cannot be greater than %d", 64) + } } return nil @@ -134,23 +132,21 @@ func (m *MsgEndRequest) Route() string { } func (m *MsgEndRequest) Type() string { - return fmt.Sprintf("%s:end", ModuleName) + return TypeMsgEndRequest } func (m *MsgEndRequest) ValidateBasic() error { - // From shouldn't be nil or empty + if m.From == "" { + return errors.Wrap(ErrorInvalidFrom, "from cannot be empty") + } if _, err := sdk.AccAddressFromBech32(m.From); err != nil { - return errors.Wrapf(ErrorInvalidField, "%s", "from") + return errors.Wrapf(ErrorInvalidFrom, "%s", err) } - - // Id should be positive if m.Id == 0 { - return errors.Wrapf(ErrorInvalidField, "%s", "id") + return errors.Wrap(ErrorInvalidId, "id cannot be zero") } - - // Rating shouldn't be greater than 10 if m.Rating > 10 { - return errors.Wrapf(ErrorInvalidField, "%s", "rating") + return errors.Wrapf(ErrorInvalidRating, "rating cannot be greater than %d", 10) } return nil diff --git a/x/session/types/msg_test.go b/x/session/types/msg_test.go new file mode 100644 index 00000000..96f77959 --- /dev/null +++ b/x/session/types/msg_test.go @@ -0,0 +1,591 @@ +package types + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + + hubtypes "github.com/sentinel-official/hub/types" +) + +func TestMsgStartRequest_ValidateBasic(t *testing.T) { + type fields struct { + From string + Id uint64 + Node string + } + tests := []struct { + name string + fields fields + wantErr bool + }{ + { + "empty from", + fields{ + From: "", + }, + true, + }, + { + "invalid from", + fields{ + From: "invalid", + }, + true, + }, + { + "invalid prefix from", + fields{ + From: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + }, + true, + }, + { + "10 bytes from", + fields{ + From: "sent1qypqxpq9qcrsszgslawd5s", + }, + true, + }, + { + "20 bytes from", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + }, + true, + }, + { + "30 bytes from", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfqyy3zxfp9ycnjs2fszvfck8", + }, + true, + }, + { + "zero id", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Id: 0, + }, + true, + }, + { + "positive id", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Id: 1000, + }, + true, + }, + { + "empty node", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Id: 1000, + Node: "", + }, + true, + }, + { + "invalid node", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Id: 1000, + Node: "invalid", + }, + true, + }, + { + "invalid prefix node", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Id: 1000, + Node: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + }, + true, + }, + { + "10 bytes node", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Id: 1000, + Node: "sentnode1qypqxpq9qcrsszgse4wwrm", + }, + true, + }, + { + "20 bytes node", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Id: 1000, + Node: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + }, + false, + }, + { + "30 bytes node", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Id: 1000, + Node: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqyy3zxfp9ycnjs2fsxqglcv", + }, + true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &MsgStartRequest{ + From: tt.fields.From, + Id: tt.fields.Id, + Node: tt.fields.Node, + } + if err := m.ValidateBasic(); (err != nil) != tt.wantErr { + t.Errorf("ValidateBasic() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestMsgUpdateRequest_ValidateBasic(t *testing.T) { + type fields struct { + From string + Proof Proof + Signature []byte + } + tests := []struct { + name string + fields fields + wantErr bool + }{ + { + "empty from", + fields{ + From: "", + }, + true, + }, + { + "invalid from", + fields{ + From: "invalid", + }, + true, + }, + { + "invalid prefix from", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + }, + true, + }, + { + "10 bytes from", + fields{ + From: "sentnode1qypqxpq9qcrsszgse4wwrm", + }, + true, + }, + { + "20 bytes from", + fields{ + From: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + }, + true, + }, + { + "30 bytes from", + fields{ + From: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqyy3zxfp9ycnjs2fsxqglcv", + }, + true, + }, + { + "zero proof->id", + fields{ + From: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Proof: Proof{ + Id: 0, + }, + }, + true, + }, + { + "positive proof->id", + fields{ + From: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Proof: Proof{ + Id: 1000, + Bandwidth: hubtypes.Bandwidth{Upload: sdk.NewInt(0), Download: sdk.NewInt(0)}, + }, + }, + false, + }, + { + "negative proof->duration", + fields{ + From: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Proof: Proof{ + Id: 1000, + Duration: -1000, + }, + }, + true, + }, + { + "zero proof->duration", + fields{ + From: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Proof: Proof{ + Id: 1000, + Duration: 0, + Bandwidth: hubtypes.Bandwidth{Upload: sdk.NewInt(0), Download: sdk.NewInt(0)}, + }, + }, + false, + }, + { + "positive proof->duration", + fields{ + From: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Proof: Proof{ + Id: 1000, + Duration: 1000, + Bandwidth: hubtypes.Bandwidth{Upload: sdk.NewInt(0), Download: sdk.NewInt(0)}, + }, + }, + false, + }, + { + "negative proof->bandwidth->upload and negative proof->bandwidth->download", + fields{ + From: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Proof: Proof{ + Id: 1000, + Duration: 1000, + Bandwidth: hubtypes.Bandwidth{Upload: sdk.NewInt(-1000), Download: sdk.NewInt(-1000)}, + }, + }, + true, + }, + { + "negative proof->bandwidth->upload and zero proof->bandwidth->download", + fields{ + From: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Proof: Proof{ + Id: 1000, + Duration: 1000, + Bandwidth: hubtypes.Bandwidth{Upload: sdk.NewInt(-1000), Download: sdk.NewInt(0)}, + }, + }, + true, + }, + { + "negative proof->bandwidth->upload and positive proof->bandwidth->download", + fields{ + From: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Proof: Proof{ + Id: 1000, + Duration: 1000, + Bandwidth: hubtypes.Bandwidth{Upload: sdk.NewInt(-1000), Download: sdk.NewInt(1000)}, + }, + }, + true, + }, + { + "zero proof->bandwidth->upload and negative proof->bandwidth->download", + fields{ + From: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Proof: Proof{ + Id: 1000, + Duration: 1000, + Bandwidth: hubtypes.Bandwidth{Upload: sdk.NewInt(0), Download: sdk.NewInt(-1000)}, + }, + }, + true, + }, + { + "zero proof->bandwidth->upload and zero proof->bandwidth->download", + fields{ + From: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Proof: Proof{ + Id: 1000, + Duration: 1000, + Bandwidth: hubtypes.Bandwidth{Upload: sdk.NewInt(0), Download: sdk.NewInt(0)}, + }, + }, + false, + }, + { + "zero proof->bandwidth->upload and positive proof->bandwidth->download", + fields{ + From: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Proof: Proof{ + Id: 1000, + Duration: 1000, + Bandwidth: hubtypes.Bandwidth{Upload: sdk.NewInt(0), Download: sdk.NewInt(1000)}, + }, + }, + false, + }, + { + "positive proof->bandwidth->upload and negative proof->bandwidth->download", + fields{ + From: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Proof: Proof{ + Id: 1000, + Duration: 1000, + Bandwidth: hubtypes.Bandwidth{Upload: sdk.NewInt(1000), Download: sdk.NewInt(-1000)}, + }, + }, + true, + }, + { + "positive proof->bandwidth->upload and zero proof->bandwidth->download", + fields{ + From: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Proof: Proof{ + Id: 1000, + Duration: 1000, + Bandwidth: hubtypes.Bandwidth{Upload: sdk.NewInt(1000), Download: sdk.NewInt(0)}, + }, + }, + false, + }, + { + "positive proof->bandwidth->upload and positive proof->bandwidth->download", + fields{ + From: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Proof: Proof{ + Id: 1000, + Duration: 1000, + Bandwidth: hubtypes.Bandwidth{Upload: sdk.NewInt(1000), Download: sdk.NewInt(1000)}, + }, + }, + false, + }, + { + "nil signature", + fields{ + From: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Proof: Proof{ + Id: 1000, + Duration: 1000, + Bandwidth: hubtypes.Bandwidth{Upload: sdk.NewInt(1000), Download: sdk.NewInt(1000)}, + }, + Signature: nil, + }, + false, + }, + { + "empty signature", + fields{ + From: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Proof: Proof{ + Id: 1000, + Duration: 1000, + Bandwidth: hubtypes.Bandwidth{Upload: sdk.NewInt(1000), Download: sdk.NewInt(1000)}, + }, + Signature: []byte{}, + }, + true, + }, + { + "32 byte signature", + fields{ + From: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Proof: Proof{ + Id: 1000, + Duration: 1000, + Bandwidth: hubtypes.Bandwidth{Upload: sdk.NewInt(1000), Download: sdk.NewInt(1000)}, + }, + Signature: []byte{ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, + 0x16, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22, 0x23, + 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30, 0x31, + }, + }, + true, + }, + { + "64 byte signature", + fields{ + From: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Proof: Proof{ + Id: 1000, + Duration: 1000, + Bandwidth: hubtypes.Bandwidth{Upload: sdk.NewInt(1000), Download: sdk.NewInt(1000)}, + }, + Signature: []byte{ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, + 0x16, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22, 0x23, + 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30, 0x31, + 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, + 0x56, 0x57, 0x58, 0x59, 0x60, 0x61, 0x62, 0x63, + }, + }, + false, + }, + { + "96 byte signature", + fields{ + From: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Proof: Proof{ + Id: 1000, + Duration: 1000, + Bandwidth: hubtypes.Bandwidth{Upload: sdk.NewInt(1000), Download: sdk.NewInt(1000)}, + }, + Signature: []byte{ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, + 0x16, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22, 0x23, + 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30, 0x31, + 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, + 0x56, 0x57, 0x58, 0x59, 0x60, 0x61, 0x62, 0x63, + 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x70, 0x71, + 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, + }, + }, + true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &MsgUpdateRequest{ + From: tt.fields.From, + Proof: tt.fields.Proof, + Signature: tt.fields.Signature, + } + if err := m.ValidateBasic(); (err != nil) != tt.wantErr { + t.Errorf("ValidateBasic() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestMsgEndRequest_ValidateBasic(t *testing.T) { + type fields struct { + From string + Id uint64 + Rating uint64 + } + tests := []struct { + name string + fields fields + wantErr bool + }{ + { + "empty from", + fields{ + From: "", + }, + true, + }, + { + "invalid from", + fields{ + From: "invalid", + }, + true, + }, + { + "invalid prefix from", + fields{ + From: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + }, + true, + }, + { + "10 bytes from", + fields{ + From: "sent1qypqxpq9qcrsszgslawd5s", + }, + true, + }, + { + "20 bytes from", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + }, + true, + }, + { + "30 bytes from", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfqyy3zxfp9ycnjs2fszvfck8", + }, + true, + }, + { + "zero id", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Id: 0, + }, + true, + }, + { + "positive id", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Id: 1000, + }, + false, + }, + { + "zero rating", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Id: 1000, + Rating: 0, + }, + false, + }, + { + "5 rating", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Id: 1000, + Rating: 5, + }, + false, + }, + { + "10 rating", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Id: 1000, + Rating: 10, + }, + false, + }, + { + "15 rating", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Id: 1000, + Rating: 15, + }, + true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &MsgEndRequest{ + From: tt.fields.From, + Id: tt.fields.Id, + Rating: tt.fields.Rating, + } + if err := m.ValidateBasic(); (err != nil) != tt.wantErr { + t.Errorf("ValidateBasic() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/x/session/types/params.go b/x/session/types/params.go index 922ab978..0bf51a69 100644 --- a/x/session/types/params.go +++ b/x/session/types/params.go @@ -21,27 +21,33 @@ var ( _ params.ParamSet = (*Params)(nil) ) -func (p *Params) Validate() error { - if p.InactiveDuration <= 0 { - return fmt.Errorf("inactive_duration should be positive") +func (m *Params) Validate() error { + if m.InactiveDuration < 0 { + return fmt.Errorf("inactive_duration cannot be negative") + } + if m.InactiveDuration == 0 { + return fmt.Errorf("inactive_duration cannot be zero") } return nil } -func (p *Params) ParamSetPairs() params.ParamSetPairs { +func (m *Params) ParamSetPairs() params.ParamSetPairs { return params.ParamSetPairs{ { Key: KeyInactiveDuration, - Value: &p.InactiveDuration, + Value: &m.InactiveDuration, ValidatorFn: func(v interface{}) error { value, ok := v.(time.Duration) if !ok { return fmt.Errorf("invalid parameter type %T", v) } - if value <= 0 { - return fmt.Errorf("inactive duration value should be positive") + if value < 0 { + return fmt.Errorf("value cannot be negative") + } + if value == 0 { + return fmt.Errorf("value cannot be zero") } return nil @@ -49,7 +55,7 @@ func (p *Params) ParamSetPairs() params.ParamSetPairs { }, { Key: KeyProofVerificationEnabled, - Value: &p.ProofVerificationEnabled, + Value: &m.ProofVerificationEnabled, ValidatorFn: func(v interface{}) error { _, ok := v.(bool) if !ok { diff --git a/x/session/types/params_test.go b/x/session/types/params_test.go new file mode 100644 index 00000000..d91f2f29 --- /dev/null +++ b/x/session/types/params_test.go @@ -0,0 +1,51 @@ +package types + +import ( + "testing" + "time" +) + +func TestParams_Validate(t *testing.T) { + type fields struct { + InactiveDuration time.Duration + ProofVerificationEnabled bool + } + tests := []struct { + name string + fields fields + wantErr bool + }{ + { + "negative inactive duration", + fields{ + InactiveDuration: -1000, + }, + true, + }, + { + "zero inactive duration", + fields{ + InactiveDuration: 0, + }, + true, + }, + { + "positive inactive duration", + fields{ + InactiveDuration: 1000, + }, + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &Params{ + InactiveDuration: tt.fields.InactiveDuration, + ProofVerificationEnabled: tt.fields.ProofVerificationEnabled, + } + if err := m.Validate(); (err != nil) != tt.wantErr { + t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/x/session/types/session.go b/x/session/types/session.go index 7e5c0c69..b80be3c0 100644 --- a/x/session/types/session.go +++ b/x/session/types/session.go @@ -4,16 +4,17 @@ import ( "fmt" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/errors" hubtypes "github.com/sentinel-official/hub/types" ) -func (s *Session) GetAddress() sdk.AccAddress { - if s.Address == "" { +func (m *Session) GetAddress() sdk.AccAddress { + if m.Address == "" { return nil } - address, err := sdk.AccAddressFromBech32(s.Address) + address, err := sdk.AccAddressFromBech32(m.Address) if err != nil { panic(err) } @@ -21,12 +22,12 @@ func (s *Session) GetAddress() sdk.AccAddress { return address } -func (s *Session) GetNode() hubtypes.NodeAddress { - if s.Node == "" { +func (m *Session) GetNode() hubtypes.NodeAddress { + if m.Node == "" { return nil } - address, err := hubtypes.NodeAddressFromBech32(s.Node) + address, err := hubtypes.NodeAddressFromBech32(m.Node) if err != nil { panic(err) } @@ -34,30 +35,36 @@ func (s *Session) GetNode() hubtypes.NodeAddress { return address } -func (s *Session) Validate() error { - if s.Id == 0 { - return fmt.Errorf("id should not be zero") +func (m *Session) Validate() error { + if m.Id == 0 { + return fmt.Errorf("id cannot be zero") } - if s.Subscription == 0 { - return fmt.Errorf("subscription should not be zero") + if m.Subscription == 0 { + return fmt.Errorf("subscription cannot be zero") } - if _, err := hubtypes.NodeAddressFromBech32(s.Node); err != nil { - return fmt.Errorf("node should not be nil or empty") + if m.Node == "" { + return fmt.Errorf("node cannot be empty") } - if _, err := sdk.AccAddressFromBech32(s.Address); err != nil { - return fmt.Errorf("address should not be nil or empty") + if _, err := hubtypes.NodeAddressFromBech32(m.Node); err != nil { + return errors.Wrapf(err, "invalid node %s", m.Node) } - if s.Duration <= 0 { - return fmt.Errorf("duration should be positive") + if m.Address == "" { + return fmt.Errorf("address cannot be empty") } - if s.Bandwidth.IsAllPositive() { - return fmt.Errorf("bandwidth should be valid") + if _, err := sdk.AccAddressFromBech32(m.Address); err != nil { + return errors.Wrapf(err, "invalid address %s", m.Address) } - if !s.Status.Equal(hubtypes.StatusActive) && !s.Status.Equal(hubtypes.StatusInactive) { - return fmt.Errorf("status should be either active or inactive") + if m.Duration < 0 { + return fmt.Errorf("duration cannot be negative") } - if s.StatusAt.IsZero() { - return fmt.Errorf("status_at should not be zero") + if m.Bandwidth.IsAnyNegative() { + return fmt.Errorf("bandwidth cannot be negative") + } + if !m.Status.IsValid() { + return fmt.Errorf("status must be valid") + } + if m.StatusAt.IsZero() { + return fmt.Errorf("status_at cannot be zero") } return nil diff --git a/x/session/types/session_test.go b/x/session/types/session_test.go new file mode 100644 index 00000000..56720480 --- /dev/null +++ b/x/session/types/session_test.go @@ -0,0 +1,515 @@ +package types + +import ( + "reflect" + "testing" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + + hubtypes "github.com/sentinel-official/hub/types" +) + +func TestSession_GetAddress(t *testing.T) { + type fields struct { + Id uint64 + Subscription uint64 + Node string + Address string + Duration time.Duration + Bandwidth hubtypes.Bandwidth + Status hubtypes.Status + StatusAt time.Time + } + tests := []struct { + name string + fields fields + want sdk.AccAddress + }{ + { + "empty", + fields{ + Address: "", + }, + nil, + }, + { + "20 bytes", + fields{ + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + }, + sdk.AccAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &Session{ + Id: tt.fields.Id, + Subscription: tt.fields.Subscription, + Node: tt.fields.Node, + Address: tt.fields.Address, + Duration: tt.fields.Duration, + Bandwidth: tt.fields.Bandwidth, + Status: tt.fields.Status, + StatusAt: tt.fields.StatusAt, + } + if got := m.GetAddress(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("GetAddress() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSession_GetNode(t *testing.T) { + type fields struct { + Id uint64 + Subscription uint64 + Node string + Address string + Duration time.Duration + Bandwidth hubtypes.Bandwidth + Status hubtypes.Status + StatusAt time.Time + } + tests := []struct { + name string + fields fields + want hubtypes.NodeAddress + }{ + { + "empty", + fields{ + Node: "", + }, + nil, + }, + { + "20 bytes", + fields{ + Node: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + }, + hubtypes.NodeAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &Session{ + Id: tt.fields.Id, + Subscription: tt.fields.Subscription, + Node: tt.fields.Node, + Address: tt.fields.Address, + Duration: tt.fields.Duration, + Bandwidth: tt.fields.Bandwidth, + Status: tt.fields.Status, + StatusAt: tt.fields.StatusAt, + } + if got := m.GetNode(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("GetNode() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSession_Validate(t *testing.T) { + type fields struct { + Id uint64 + Subscription uint64 + Node string + Address string + Duration time.Duration + Bandwidth hubtypes.Bandwidth + Status hubtypes.Status + StatusAt time.Time + } + tests := []struct { + name string + fields fields + wantErr bool + }{ + { + "zero id", + fields{ + Id: 0, + }, + true, + }, + { + "positive id", + fields{ + Id: 1000, + }, + true, + }, + { + "zero subscription", + fields{ + Id: 1000, + Subscription: 0, + }, + true, + }, + { + "positive subscription", + fields{ + Id: 1000, + Subscription: 1000, + }, + true, + }, + { + "empty node", + fields{ + Id: 1000, + Subscription: 1000, + Node: "", + }, + true, + }, + { + "invalid node", + fields{ + Id: 1000, + Subscription: 1000, + Node: "invalid", + }, + true, + }, + { + "invalid prefix node", + fields{ + Id: 1000, + Subscription: 1000, + Node: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + }, + true, + }, + { + "10 bytes node", + fields{ + Id: 1000, + Subscription: 1000, + Node: "sentnode1qypqxpq9qcrsszgse4wwrm", + }, + true, + }, + { + "20 bytes node", + fields{ + Id: 1000, + Subscription: 1000, + Node: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + }, + true, + }, + { + "30 bytes node", + fields{ + Id: 1000, + Subscription: 1000, + Node: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqyy3zxfp9ycnjs2fsxqglcv", + }, + true, + }, + { + "empty address", + fields{ + Id: 1000, + Subscription: 1000, + Node: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Address: "", + }, + true, + }, + { + "invalid address", + fields{ + Id: 1000, + Subscription: 1000, + Node: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Address: "invalid", + }, + true, + }, + { + "invalid prefix address", + fields{ + Id: 1000, + Subscription: 1000, + Node: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Address: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + }, + true, + }, + { + "10 bytes address", + fields{ + Id: 1000, + Subscription: 1000, + Node: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Address: "sent1qypqxpq9qcrsszgslawd5s", + }, + true, + }, + { + "20 bytes address", + fields{ + Id: 1000, + Subscription: 1000, + Node: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Bandwidth: hubtypes.Bandwidth{Upload: sdk.NewInt(0), Download: sdk.NewInt(0)}, + }, + true, + }, + { + "30 bytes address", + fields{ + Id: 1000, + Subscription: 1000, + Node: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfqyy3zxfp9ycnjs2fszvfck8", + }, + true, + }, + { + "negative duration", + fields{ + Id: 1000, + Subscription: 1000, + Node: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Duration: -1000, + }, + true, + }, + { + "zero duration", + fields{ + Id: 1000, + Subscription: 1000, + Node: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Duration: 0, + Bandwidth: hubtypes.Bandwidth{Upload: sdk.NewInt(0), Download: sdk.NewInt(0)}, + }, + true, + }, + { + "positive duration", + fields{ + Id: 1000, + Subscription: 1000, + Node: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Duration: 1000, + Bandwidth: hubtypes.Bandwidth{Upload: sdk.NewInt(0), Download: sdk.NewInt(0)}, + }, + true, + }, + { + "negative upload and negative download", + fields{ + Id: 1000, + Subscription: 1000, + Node: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Duration: 1000, + Bandwidth: hubtypes.Bandwidth{Upload: sdk.NewInt(-1000), Download: sdk.NewInt(-1000)}, + }, + true, + }, + { + "negative upload and zero download", + fields{ + Id: 1000, + Subscription: 1000, + Node: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Duration: 1000, + Bandwidth: hubtypes.Bandwidth{Upload: sdk.NewInt(-1000), Download: sdk.NewInt(0)}, + }, + true, + }, + { + "negative upload and positive download", + fields{ + Id: 1000, + Subscription: 1000, + Node: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Duration: 1000, + Bandwidth: hubtypes.Bandwidth{Upload: sdk.NewInt(-1000), Download: sdk.NewInt(1000)}, + }, + true, + }, + { + "zero upload and negative download", + fields{ + Id: 1000, + Subscription: 1000, + Node: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Duration: 1000, + Bandwidth: hubtypes.Bandwidth{Upload: sdk.NewInt(0), Download: sdk.NewInt(-1000)}, + }, + true, + }, + { + "zero upload and zero download", + fields{ + Id: 1000, + Subscription: 1000, + Node: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Duration: 1000, + Bandwidth: hubtypes.Bandwidth{Upload: sdk.NewInt(0), Download: sdk.NewInt(0)}, + }, + true, + }, + { + "zero upload and positive download", + fields{ + Id: 1000, + Subscription: 1000, + Node: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Duration: 1000, + Bandwidth: hubtypes.Bandwidth{Upload: sdk.NewInt(0), Download: sdk.NewInt(1000)}, + }, + true, + }, + { + "positive upload and negative download", + fields{ + Id: 1000, + Subscription: 1000, + Node: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Duration: 1000, + Bandwidth: hubtypes.Bandwidth{Upload: sdk.NewInt(1000), Download: sdk.NewInt(-1000)}, + }, + true, + }, + { + "positive upload and zero download", + fields{ + Id: 1000, + Subscription: 1000, + Node: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Duration: 1000, + Bandwidth: hubtypes.Bandwidth{Upload: sdk.NewInt(1000), Download: sdk.NewInt(0)}, + }, + true, + }, + { + "positive upload and positive download", + fields{ + Id: 1000, + Subscription: 1000, + Node: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Duration: 1000, + Bandwidth: hubtypes.Bandwidth{Upload: sdk.NewInt(1000), Download: sdk.NewInt(1000)}, + }, + true, + }, + { + "unknown status", + fields{ + Id: 1000, + Subscription: 1000, + Node: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Duration: 1000, + Bandwidth: hubtypes.Bandwidth{Upload: sdk.NewInt(1000), Download: sdk.NewInt(1000)}, + Status: hubtypes.StatusUnknown, + }, + true, + }, + { + "active status", + fields{ + Id: 1000, + Subscription: 1000, + Node: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Duration: 1000, + Bandwidth: hubtypes.Bandwidth{Upload: sdk.NewInt(1000), Download: sdk.NewInt(1000)}, + Status: hubtypes.Active, + }, + true, + }, + { + "inactive pending status", + fields{ + Id: 1000, + Subscription: 1000, + Node: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Duration: 1000, + Bandwidth: hubtypes.Bandwidth{Upload: sdk.NewInt(1000), Download: sdk.NewInt(1000)}, + Status: hubtypes.StatusInactivePending, + }, + true, + }, + { + "inactive status", + fields{ + Id: 1000, + Subscription: 1000, + Node: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Duration: 1000, + Bandwidth: hubtypes.Bandwidth{Upload: sdk.NewInt(1000), Download: sdk.NewInt(1000)}, + Status: hubtypes.Inactive, + }, + true, + }, + { + "zero status", + fields{ + Id: 1000, + Subscription: 1000, + Node: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Duration: 1000, + Bandwidth: hubtypes.Bandwidth{Upload: sdk.NewInt(1000), Download: sdk.NewInt(1000)}, + Status: hubtypes.Inactive, + StatusAt: time.Time{}, + }, + true, + }, + { + "now status", + fields{ + Id: 1000, + Subscription: 1000, + Node: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Duration: 1000, + Bandwidth: hubtypes.Bandwidth{Upload: sdk.NewInt(1000), Download: sdk.NewInt(1000)}, + Status: hubtypes.Inactive, + StatusAt: time.Now(), + }, + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &Session{ + Id: tt.fields.Id, + Subscription: tt.fields.Subscription, + Node: tt.fields.Node, + Address: tt.fields.Address, + Duration: tt.fields.Duration, + Bandwidth: tt.fields.Bandwidth, + Status: tt.fields.Status, + StatusAt: tt.fields.StatusAt, + } + if err := m.Validate(); (err != nil) != tt.wantErr { + t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/x/subscription/expected/keeper.go b/x/subscription/expected/keeper.go index 746525c5..51363e83 100644 --- a/x/subscription/expected/keeper.go +++ b/x/subscription/expected/keeper.go @@ -15,6 +15,7 @@ type AccountKeeper interface { type BankKeeper interface { SendCoins(ctx sdk.Context, from sdk.AccAddress, to sdk.AccAddress, coins sdk.Coins) error + SpendableCoins(ctx sdk.Context, address sdk.AccAddress) sdk.Coins } type DepositKeeper interface { @@ -24,12 +25,8 @@ type DepositKeeper interface { type NodeKeeper interface { GetNode(ctx sdk.Context, address hubtypes.NodeAddress) (nodetypes.Node, bool) - GetNodes(ctx sdk.Context, skip, limit int64) nodetypes.Nodes - GetActiveNodes(ctx sdk.Context, skip, limit int64) nodetypes.Nodes } type PlanKeeper interface { GetPlan(ctx sdk.Context, id uint64) (plantypes.Plan, bool) - GetPlans(ctx sdk.Context, skip, limit int64) plantypes.Plans - GetActivePlans(ctx sdk.Context, skip, limit int64) plantypes.Plans } diff --git a/x/subscription/keeper/keeper.go b/x/subscription/keeper/keeper.go index 9d0283c3..d1a51014 100644 --- a/x/subscription/keeper/keeper.go +++ b/x/subscription/keeper/keeper.go @@ -21,6 +21,7 @@ type Keeper struct { deposit expected.DepositKeeper node expected.NodeKeeper plan expected.PlanKeeper + account expected.AccountKeeper } func NewKeeper(cdc codec.BinaryMarshaler, key sdk.StoreKey, params paramstypes.Subspace) Keeper { @@ -31,6 +32,10 @@ func NewKeeper(cdc codec.BinaryMarshaler, key sdk.StoreKey, params paramstypes.S } } +func (k *Keeper) WithAccountKeeper(keeper expected.AccountKeeper) { + k.account = keeper +} + func (k *Keeper) WithBankKeeper(keeper expected.BankKeeper) { k.bank = keeper } diff --git a/x/subscription/simulation/decoder.go b/x/subscription/simulation/decoder.go new file mode 100644 index 00000000..aabdd568 --- /dev/null +++ b/x/subscription/simulation/decoder.go @@ -0,0 +1,69 @@ +package simulation + +import ( + "bytes" + "fmt" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/types/kv" + protobuftypes "github.com/gogo/protobuf/types" + + "github.com/sentinel-official/hub/x/subscription/types" +) + +func NewStoreDecoder(cdc codec.Marshaler) func(kvA, kvB kv.Pair) string { + return func(kvA, kvB kv.Pair) string { + switch { + case bytes.Equal(kvA.Key[:1], types.CountKey): + var countA, countB protobuftypes.UInt64Value + cdc.MustUnmarshalBinaryBare(kvA.Value, &countA) + cdc.MustUnmarshalBinaryBare(kvB.Value, &countB) + + return fmt.Sprintf("%v\n%v", &countA, &countB) + case bytes.Equal(kvA.Key[:1], types.SubscriptionKeyPrefix): + var subscriptionA, subscriptionB types.Subscription + cdc.MustUnmarshalBinaryBare(kvA.Value, &subscriptionA) + cdc.MustUnmarshalBinaryBare(kvB.Value, &subscriptionB) + + return fmt.Sprintf("%v\n%v", &subscriptionA, &subscriptionB) + case bytes.Equal(kvA.Key[:1], types.SubscriptionForNodeKeyPrefix): + var subscriptionForNodeA, subscriptionForNodeB protobuftypes.BoolValue + cdc.MustUnmarshalBinaryBare(kvA.Value, &subscriptionForNodeA) + cdc.MustUnmarshalBinaryBare(kvB.Value, &subscriptionForNodeB) + + return fmt.Sprintf("%v\n%v", &subscriptionForNodeA, &subscriptionForNodeB) + case bytes.Equal(kvA.Key[:1], types.SubscriptionForPlanKeyPrefix): + var subscriptionForPlanA, subscriptionForPlanB protobuftypes.BoolValue + cdc.MustUnmarshalBinaryBare(kvA.Value, &subscriptionForPlanA) + cdc.MustUnmarshalBinaryBare(kvB.Value, &subscriptionForPlanB) + + return fmt.Sprintf("%v\n%v", &subscriptionForPlanA, &subscriptionForPlanB) + case bytes.Equal(kvA.Key[:1], types.ActiveSubscriptionForAddressKeyPrefix): + var activeSubscriptionForAddressA, activeSubscriptionForAddressB protobuftypes.BoolValue + cdc.MustUnmarshalBinaryBare(kvA.Value, &activeSubscriptionForAddressA) + cdc.MustUnmarshalBinaryBare(kvB.Value, &activeSubscriptionForAddressB) + + return fmt.Sprintf("%v\n%v", &activeSubscriptionForAddressA, &activeSubscriptionForAddressB) + case bytes.Equal(kvA.Key[:1], types.InactiveSubscriptionForAddressKeyPrefix): + var inactiveSubscriptionForAddressA, inactiveSubscriptionForAddressB protobuftypes.BoolValue + cdc.MustUnmarshalBinaryBare(kvA.Value, &inactiveSubscriptionForAddressA) + cdc.MustUnmarshalBinaryBare(kvB.Value, &inactiveSubscriptionForAddressB) + + return fmt.Sprintf("%v\n%v", &inactiveSubscriptionForAddressA, &inactiveSubscriptionForAddressB) + case bytes.Equal(kvA.Key[:1], types.InactiveSubscriptionAtKeyPrefix): + var inactiveSubscriptionAtA, inactiveSubscriptionAtB protobuftypes.BoolValue + cdc.MustUnmarshalBinaryBare(kvA.Value, &inactiveSubscriptionAtA) + cdc.MustUnmarshalBinaryBare(kvB.Value, &inactiveSubscriptionAtB) + + return fmt.Sprintf("%v\n%v", &inactiveSubscriptionAtA, &inactiveSubscriptionAtB) + case bytes.Equal(kvA.Key[:1], types.QuotaKeyPrefix): + var quotaA, quotaB types.Quota + cdc.MustUnmarshalBinaryBare(kvA.Value, "aA) + cdc.MustUnmarshalBinaryBare(kvB.Value, "aB) + + return fmt.Sprintf("%v\n%v", "aA, "aB) + } + + panic(fmt.Sprintf("invalid key prefix %X", kvA.Key[:1])) + } +} diff --git a/x/subscription/simulation/genesis.go b/x/subscription/simulation/genesis.go new file mode 100644 index 00000000..a886fa81 --- /dev/null +++ b/x/subscription/simulation/genesis.go @@ -0,0 +1,31 @@ +package simulation + +import ( + "math/rand" + "time" + + "github.com/cosmos/cosmos-sdk/types/module" + + "github.com/sentinel-official/hub/x/subscription/types" +) + +func RandomizedGenesisState(state *module.SimulationState) *types.GenesisState { + var ( + inactiveDuration time.Duration + ) + + state.AppParams.GetOrGenerate( + state.Cdc, + string(types.KeyInactiveDuration), + &inactiveDuration, + state.Rand, + func(r *rand.Rand) { + inactiveDuration = time.Duration(r.Int63n(MaxInactiveDuration)) * time.Millisecond + }, + ) + + return types.NewGenesisState( + nil, + types.NewParams(inactiveDuration), + ) +} diff --git a/x/subscription/simulation/operations.go b/x/subscription/simulation/operations.go new file mode 100644 index 00000000..e981dfc0 --- /dev/null +++ b/x/subscription/simulation/operations.go @@ -0,0 +1,479 @@ +package simulation + +import ( + "math" + "math/rand" + + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/simapp/helpers" + "github.com/cosmos/cosmos-sdk/simapp/params" + sdk "github.com/cosmos/cosmos-sdk/types" + simulationtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/cosmos/cosmos-sdk/x/simulation" + + hubtypes "github.com/sentinel-official/hub/types" + simulationhubtypes "github.com/sentinel-official/hub/types/simulation" + "github.com/sentinel-official/hub/x/subscription/expected" + "github.com/sentinel-official/hub/x/subscription/keeper" + "github.com/sentinel-official/hub/x/subscription/types" +) + +var ( + OperationWeightMsgSubscribeToNodeRequest = "op_weight_" + types.TypeMsgSubscribeToNodeRequest + OperationWeightMsgSubscribeToPlanRequest = "op_weight_" + types.TypeMsgSubscribeToPlanRequest + OperationWeightMsgCancelRequest = "op_weight_" + types.TypeMsgCancelRequest + OperationWeightMsgAddQuotaRequest = "op_weight_" + types.TypeMsgAddQuotaRequest + OperationWeightMsgUpdateQuotaRequest = "op_weight_" + types.TypeMsgUpdateQuotaRequest +) + +func WeightedOperations( + params simulationtypes.AppParams, + cdc codec.JSONMarshaler, + ak expected.AccountKeeper, + bk expected.BankKeeper, + k keeper.Keeper, +) simulation.WeightedOperations { + var ( + weightMsgSubscribeToNodeRequest int + weightMsgSubscribeToPlanRequest int + weightMsgCancelRequest int + weightMsgAddQuotaRequest int + weightMsgUpdateQuotaRequest int + ) + + params.GetOrGenerate( + cdc, + OperationWeightMsgSubscribeToNodeRequest, + &weightMsgSubscribeToNodeRequest, + nil, + func(_ *rand.Rand) { + weightMsgSubscribeToNodeRequest = 100 + }, + ) + params.GetOrGenerate( + cdc, + OperationWeightMsgSubscribeToPlanRequest, + &weightMsgSubscribeToPlanRequest, + nil, + func(_ *rand.Rand) { + weightMsgSubscribeToPlanRequest = 100 + }, + ) + params.GetOrGenerate( + cdc, + OperationWeightMsgCancelRequest, + &weightMsgCancelRequest, + nil, + func(_ *rand.Rand) { + weightMsgCancelRequest = 100 + }, + ) + params.GetOrGenerate( + cdc, + OperationWeightMsgAddQuotaRequest, + &weightMsgAddQuotaRequest, + nil, + func(_ *rand.Rand) { + weightMsgAddQuotaRequest = 100 + }, + ) + params.GetOrGenerate( + cdc, + OperationWeightMsgUpdateQuotaRequest, + &weightMsgUpdateQuotaRequest, + nil, + func(_ *rand.Rand) { + weightMsgUpdateQuotaRequest = 100 + }, + ) + + return simulation.WeightedOperations{ + simulation.NewWeightedOperation( + weightMsgSubscribeToNodeRequest, + SimulateMsgSubscribeToNodeRequest(ak, bk, k), + ), + simulation.NewWeightedOperation( + weightMsgSubscribeToPlanRequest, + SimulateMsgSubscribeToPlanRequest(ak, bk, k), + ), + simulation.NewWeightedOperation( + weightMsgCancelRequest, + SimulateMsgCancelRequest(ak, bk, k), + ), + simulation.NewWeightedOperation( + weightMsgAddQuotaRequest, + SimulateMsgAddQuotaRequest(ak, bk, k), + ), + simulation.NewWeightedOperation( + weightMsgUpdateQuotaRequest, + SimulateMsgUpdateQuotaRequest(ak, bk, k), + ), + } +} + +func SimulateMsgSubscribeToNodeRequest(ak expected.AccountKeeper, bk expected.BankKeeper, k keeper.Keeper) simulationtypes.Operation { + return func( + r *rand.Rand, + app *baseapp.BaseApp, + ctx sdk.Context, + accounts []simulationtypes.Account, + chainID string, + ) (simulationtypes.OperationMsg, []simulationtypes.FutureOperation, error) { + var ( + rFrom, _ = simulationtypes.RandomAcc(r, accounts) + from = ak.GetAccount(ctx, rFrom.Address) + rAddress, _ = simulationtypes.RandomAcc(r, accounts) + address = ak.GetAccount(ctx, rAddress.Address) + ) + + node, found := k.GetNode(ctx, hubtypes.NodeAddress(address.GetAddress())) + if !found { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgSubscribeToNodeRequest, "node does not exist"), nil, nil + } + if node.Provider != "" { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgSubscribeToNodeRequest, "provider of the node is not empty"), nil, nil + } + if !node.Status.Equal(hubtypes.StatusActive) { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgSubscribeToNodeRequest, "node status is not active"), nil, nil + } + + var ( + deposit = simulationhubtypes.RandomCoin( + r, + sdk.NewInt64Coin( + sdk.DefaultBondDenom, + MaxSubscriptionDepositAmount, + ), + ) + ) + + _, found = node.PriceForDenom(deposit.Denom) + if !found { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgSubscribeToNodeRequest, "price for denom does not exist"), nil, nil + } + + balance := bk.SpendableCoins(ctx, from.GetAddress()) + if !balance.IsAnyNegative() { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgSubscribeToNodeRequest, "balance is negative"), nil, nil + } + + fees, err := simulationtypes.RandomFees(r, ctx, balance) + if err != nil { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgSubscribeToNodeRequest, err.Error()), nil, err + } + + var ( + txConfig = params.MakeTestEncodingConfig().TxConfig + message = types.NewMsgSubscribeToNodeRequest( + from.GetAddress(), + hubtypes.NodeAddress(address.GetAddress()), + deposit, + ) + ) + + txn, err := helpers.GenTx( + txConfig, + []sdk.Msg{message}, + fees, + helpers.DefaultGenTxGas, + chainID, + []uint64{from.GetAccountNumber()}, + []uint64{from.GetSequence()}, + rFrom.PrivKey, + ) + if err != nil { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgSubscribeToNodeRequest, err.Error()), nil, err + } + + _, _, err = app.Deliver(txConfig.TxEncoder(), txn) + if err != nil { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgSubscribeToNodeRequest, err.Error()), nil, err + } + + return simulationtypes.NewOperationMsg(message, true, ""), nil, nil + } +} + +func SimulateMsgSubscribeToPlanRequest(ak expected.AccountKeeper, bk expected.BankKeeper, k keeper.Keeper) simulationtypes.Operation { + return func( + r *rand.Rand, + app *baseapp.BaseApp, + ctx sdk.Context, + accounts []simulationtypes.Account, + chainID string, + ) (simulationtypes.OperationMsg, []simulationtypes.FutureOperation, error) { + var ( + rFrom, _ = simulationtypes.RandomAcc(r, accounts) + from = ak.GetAccount(ctx, rFrom.Address) + rId = uint64(r.Int63n(1 << 18)) + ) + + plan, found := k.GetPlan(ctx, rId) + if !found { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgSubscribeToPlanRequest, "plan does not exist"), nil, nil + } + if !plan.Status.Equal(hubtypes.Active) { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgSubscribeToPlanRequest, "plan status is not active"), nil, nil + } + + balance := bk.SpendableCoins(ctx, from.GetAddress()) + if !balance.IsAnyNegative() { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgSubscribeToPlanRequest, "balance is negative"), nil, nil + } + + fees, err := simulationtypes.RandomFees(r, ctx, balance) + if err != nil { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgSubscribeToPlanRequest, err.Error()), nil, err + } + + var ( + txConfig = params.MakeTestEncodingConfig().TxConfig + message = types.NewMsgSubscribeToPlanRequest( + from.GetAddress(), + rId, + sdk.DefaultBondDenom, + ) + ) + + txn, err := helpers.GenTx( + txConfig, + []sdk.Msg{message}, + fees, + helpers.DefaultGenTxGas, + chainID, + []uint64{from.GetAccountNumber()}, + []uint64{from.GetSequence()}, + rFrom.PrivKey, + ) + if err != nil { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgSubscribeToPlanRequest, err.Error()), nil, err + } + + _, _, err = app.Deliver(txConfig.TxEncoder(), txn) + if err != nil { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgSubscribeToPlanRequest, err.Error()), nil, err + } + + return simulationtypes.NewOperationMsg(message, true, ""), nil, nil + } +} + +func SimulateMsgCancelRequest(ak expected.AccountKeeper, bk expected.BankKeeper, k keeper.Keeper) simulationtypes.Operation { + return func( + r *rand.Rand, + app *baseapp.BaseApp, + ctx sdk.Context, + accounts []simulationtypes.Account, + chainID string, + ) (simulationtypes.OperationMsg, []simulationtypes.FutureOperation, error) { + var ( + rFrom, _ = simulationtypes.RandomAcc(r, accounts) + from = ak.GetAccount(ctx, rFrom.Address) + ) + + subscriptions := k.GetActiveSubscriptionsForAddress(ctx, from.GetAddress(), 0, 0) + if len(subscriptions) == 0 { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgCancelRequest, "active subscriptions for address does not exist"), nil, nil + } + + var ( + rSubscription = subscriptions[r.Intn(len(subscriptions))] + ) + + balance := bk.SpendableCoins(ctx, from.GetAddress()) + if !balance.IsAnyNegative() { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgCancelRequest, "balance is negative"), nil, nil + } + + fees, err := simulationtypes.RandomFees(r, ctx, balance) + if err != nil { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgCancelRequest, err.Error()), nil, err + } + + var ( + txConfig = params.MakeTestEncodingConfig().TxConfig + message = types.NewMsgCancelRequest( + from.GetAddress(), + rSubscription.Id, + ) + ) + + txn, err := helpers.GenTx( + txConfig, + []sdk.Msg{message}, + fees, + helpers.DefaultGenTxGas, + chainID, + []uint64{from.GetAccountNumber()}, + []uint64{from.GetSequence()}, + rFrom.PrivKey, + ) + if err != nil { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgCancelRequest, err.Error()), nil, err + } + + _, _, err = app.Deliver(txConfig.TxEncoder(), txn) + if err != nil { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgCancelRequest, err.Error()), nil, err + } + + return simulationtypes.NewOperationMsg(message, true, ""), nil, nil + } +} + +func SimulateMsgAddQuotaRequest(ak expected.AccountKeeper, bk expected.BankKeeper, k keeper.Keeper) simulationtypes.Operation { + return func( + r *rand.Rand, + app *baseapp.BaseApp, + ctx sdk.Context, + accounts []simulationtypes.Account, + chainID string, + ) (simulationtypes.OperationMsg, []simulationtypes.FutureOperation, error) { + var ( + rFrom, _ = simulationtypes.RandomAcc(r, accounts) + from = ak.GetAccount(ctx, rFrom.Address) + rAddress, _ = simulationtypes.RandomAcc(r, accounts) + address = ak.GetAccount(ctx, rAddress.Address) + ) + + subscriptions := k.GetActiveSubscriptionsForAddress(ctx, from.GetAddress(), 0, 0) + if len(subscriptions) == 0 { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgAddQuotaRequest, "active subscriptions for address does not exist"), nil, nil + } + + rSubscription := subscriptions[r.Intn(len(subscriptions))] + if rSubscription.Plan == 0 { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgAddQuotaRequest, "plan of the subscription is zero"), nil, nil + } + + found := k.HasQuota(ctx, rSubscription.Id, address.GetAddress()) + if found { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgAddQuotaRequest, "quota already exists"), nil, nil + } + + bytes := sdk.NewInt(r.Int63n(math.MaxInt32)) + if bytes.GT(rSubscription.Free) { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgAddQuotaRequest, "no enough quota"), nil, nil + } + + balance := bk.SpendableCoins(ctx, from.GetAddress()) + if !balance.IsAnyNegative() { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgAddQuotaRequest, "balance is negative"), nil, nil + } + + fees, err := simulationtypes.RandomFees(r, ctx, balance) + if err != nil { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgAddQuotaRequest, err.Error()), nil, err + } + + var ( + txConfig = params.MakeTestEncodingConfig().TxConfig + message = types.NewMsgAddQuotaRequest( + from.GetAddress(), + rSubscription.Id, + address.GetAddress(), + bytes, + ) + ) + + txn, err := helpers.GenTx( + txConfig, + []sdk.Msg{message}, + fees, + helpers.DefaultGenTxGas, + chainID, + []uint64{from.GetAccountNumber()}, + []uint64{from.GetSequence()}, + rFrom.PrivKey, + ) + if err != nil { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgAddQuotaRequest, err.Error()), nil, err + } + + _, _, err = app.Deliver(txConfig.TxEncoder(), txn) + if err != nil { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgAddQuotaRequest, err.Error()), nil, err + } + + return simulationtypes.NewOperationMsg(message, true, ""), nil, nil + } +} + +func SimulateMsgUpdateQuotaRequest(ak expected.AccountKeeper, bk expected.BankKeeper, k keeper.Keeper) simulationtypes.Operation { + return func( + r *rand.Rand, + app *baseapp.BaseApp, + ctx sdk.Context, + accounts []simulationtypes.Account, + chainID string, + ) (simulationtypes.OperationMsg, []simulationtypes.FutureOperation, error) { + var ( + rFrom, _ = simulationtypes.RandomAcc(r, accounts) + from = ak.GetAccount(ctx, rFrom.Address) + rAddress, _ = simulationtypes.RandomAcc(r, accounts) + address = ak.GetAccount(ctx, rAddress.Address) + ) + + subscriptions := k.GetActiveSubscriptionsForAddress(ctx, from.GetAddress(), 0, 0) + if len(subscriptions) == 0 { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgAddQuotaRequest, "active subscriptions for address does not exist"), nil, nil + } + + rSubscription := subscriptions[r.Intn(len(subscriptions))] + if rSubscription.Plan == 0 { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgAddQuotaRequest, "plan of the subscription is zero"), nil, nil + } + + quota, found := k.GetQuota(ctx, rSubscription.Id, address.GetAddress()) + if !found { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgAddQuotaRequest, "quota does not exist"), nil, nil + } + + bytes := sdk.NewInt(r.Int63n(math.MaxInt32)) + if bytes.LT(quota.Consumed) || bytes.GT(rSubscription.Free.Add(quota.Allocated)) { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgAddQuotaRequest, "no enough quota"), nil, nil + } + + balance := bk.SpendableCoins(ctx, from.GetAddress()) + if !balance.IsAnyNegative() { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgUpdateQuotaRequest, "balance is negative"), nil, nil + } + + fees, err := simulationtypes.RandomFees(r, ctx, balance) + if err != nil { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgUpdateQuotaRequest, err.Error()), nil, err + } + + var ( + txConfig = params.MakeTestEncodingConfig().TxConfig + message = types.NewMsgUpdateQuotaRequest( + from.GetAddress(), + rSubscription.Id, + address.GetAddress(), + bytes, + ) + ) + + txn, err := helpers.GenTx( + txConfig, + []sdk.Msg{message}, + fees, + helpers.DefaultGenTxGas, + chainID, + []uint64{from.GetAccountNumber()}, + []uint64{from.GetSequence()}, + rFrom.PrivKey, + ) + if err != nil { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgUpdateQuotaRequest, err.Error()), nil, err + } + + _, _, err = app.Deliver(txConfig.TxEncoder(), txn) + if err != nil { + return simulationtypes.NoOpMsg(types.ModuleName, types.TypeMsgUpdateQuotaRequest, err.Error()), nil, err + } + + return simulationtypes.NewOperationMsg(message, true, ""), nil, nil + } +} diff --git a/x/subscription/simulation/params.go b/x/subscription/simulation/params.go new file mode 100644 index 00000000..e97612ef --- /dev/null +++ b/x/subscription/simulation/params.go @@ -0,0 +1,31 @@ +package simulation + +import ( + "fmt" + "math/rand" + "time" + + simulationtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/cosmos/cosmos-sdk/x/simulation" + + "github.com/sentinel-official/hub/x/subscription/types" +) + +const ( + MaxInactiveDuration = 1 << 18 +) + +func ParamChanges(_ *rand.Rand) []simulationtypes.ParamChange { + return []simulationtypes.ParamChange{ + simulation.NewSimParamChange( + types.ModuleName, + string(types.KeyInactiveDuration), + func(r *rand.Rand) string { + return fmt.Sprintf( + "%s", + time.Duration(r.Int63n(MaxInactiveDuration))*time.Millisecond, + ) + }, + ), + } +} diff --git a/x/subscription/simulation/rand.go b/x/subscription/simulation/rand.go new file mode 100644 index 00000000..88cf77ff --- /dev/null +++ b/x/subscription/simulation/rand.go @@ -0,0 +1,5 @@ +package simulation + +const ( + MaxSubscriptionDepositAmount = 1 << 18 +) diff --git a/x/subscription/types/errors.go b/x/subscription/types/errors.go index 2b091453..12869afb 100644 --- a/x/subscription/types/errors.go +++ b/x/subscription/types/errors.go @@ -5,18 +5,27 @@ import ( ) var ( - ErrorInvalidField = errors.Register(ModuleName, 101, "invalid field") - ErrorPlanDoesNotExist = errors.Register(ModuleName, 102, "plan does not exist") - ErrorNodeDoesNotExist = errors.Register(ModuleName, 103, "node does not exist") - ErrorUnauthorized = errors.Register(ModuleName, 104, "unauthorized") - ErrorInvalidPlanStatus = errors.Register(ModuleName, 105, "invalid plan status") - ErrorPriceDoesNotExist = errors.Register(ModuleName, 106, "price does not exist") - ErrorInvalidNodeStatus = errors.Register(ModuleName, 107, "invalid node status") - ErrorSubscriptionDoesNotExist = errors.Register(ModuleName, 108, "subscription does not exist") - ErrorInvalidSubscriptionStatus = errors.Register(ModuleName, 109, "invalid subscription status") - ErrorCanNotSubscribe = errors.Register(ModuleName, 110, "can not subscribe") - ErrorInvalidQuota = errors.Register(ModuleName, 111, "invalid quota") - ErrorDuplicateQuota = errors.Register(ModuleName, 112, "duplicate quota") - ErrorQuotaDoesNotExist = errors.Register(ModuleName, 113, "quota does not exist") - ErrorCanNotAddQuota = errors.Register(ModuleName, 114, "can not add quota") + ErrorInvalidField = errors.Register(ModuleName, 101, "invalid field") + ErrorInvalidFrom = errors.Register(ModuleName, 102, "invalid from") + ErrorInvalidAddress = errors.Register(ModuleName, 103, "invalid address") + ErrorInvalidDeposit = errors.Register(ModuleName, 104, "invalid deposit") + ErrorInvalidId = errors.Register(ModuleName, 105, "invalid id") + ErrorInvalidDenom = errors.Register(ModuleName, 106, "invalid denom") + ErrorInvalidBytes = errors.Register(ModuleName, 107, "invalid bytes") +) + +var ( + ErrorPlanDoesNotExist = errors.Register(ModuleName, 201, "plan does not exist") + ErrorNodeDoesNotExist = errors.Register(ModuleName, 202, "node does not exist") + ErrorUnauthorized = errors.Register(ModuleName, 203, "unauthorized") + ErrorInvalidPlanStatus = errors.Register(ModuleName, 204, "invalid plan status") + ErrorPriceDoesNotExist = errors.Register(ModuleName, 205, "price does not exist") + ErrorInvalidNodeStatus = errors.Register(ModuleName, 206, "invalid node status") + ErrorSubscriptionDoesNotExist = errors.Register(ModuleName, 207, "subscription does not exist") + ErrorInvalidSubscriptionStatus = errors.Register(ModuleName, 208, "invalid subscription status") + ErrorCanNotSubscribe = errors.Register(ModuleName, 209, "can not subscribe") + ErrorInvalidQuota = errors.Register(ModuleName, 210, "invalid quota") + ErrorDuplicateQuota = errors.Register(ModuleName, 211, "duplicate quota") + ErrorQuotaDoesNotExist = errors.Register(ModuleName, 212, "quota does not exist") + ErrorCanNotAddQuota = errors.Register(ModuleName, 213, "can not add quota") ) diff --git a/x/subscription/types/genesis.go b/x/subscription/types/genesis.go index fda00df5..757684c6 100644 --- a/x/subscription/types/genesis.go +++ b/x/subscription/types/genesis.go @@ -24,37 +24,37 @@ func ValidateGenesis(state *GenesisState) error { return err } - for _, item := range state.Subscriptions { - if err := item.Subscription.Validate(); err != nil { - return err - } - } - subscriptions := make(map[uint64]bool) for _, item := range state.Subscriptions { if subscriptions[item.Subscription.Id] { - return fmt.Errorf("duplicate subscription id %d", item.Subscription.Id) + return fmt.Errorf("found duplicate subscription for id %d", item.Subscription.Id) } subscriptions[item.Subscription.Id] = true } for _, item := range state.Subscriptions { + quotas := make(map[string]bool) for _, quota := range item.Quotas { - if err := quota.Validate(); err != nil { - return err + if quotas[quota.Address] { + return fmt.Errorf("found duplicate quota for subscription %d and address %s", item.Subscription.Id, quota.Address) } + + quotas[quota.Address] = true + } + } + + for _, item := range state.Subscriptions { + if err := item.Subscription.Validate(); err != nil { + return err } } for _, item := range state.Subscriptions { - quotas := make(map[string]bool) for _, quota := range item.Quotas { - if quotas[quota.Address] { - return fmt.Errorf("duplicate quota for subscription %d", item.Subscription.Id) + if err := quota.Validate(); err != nil { + return err } - - quotas[quota.Address] = true } } diff --git a/x/subscription/types/genesis_test.go b/x/subscription/types/genesis_test.go new file mode 100644 index 00000000..602b071e --- /dev/null +++ b/x/subscription/types/genesis_test.go @@ -0,0 +1,61 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestDefaultGenesisState(t *testing.T) { + var ( + state *GenesisState + ) + + state = DefaultGenesisState() + require.Equal(t, &GenesisState{ + Subscriptions: nil, + Params: DefaultParams(), + }, state) +} + +func TestNewGenesisState(t *testing.T) { + var ( + subscriptions GenesisSubscriptions + params Params + state *GenesisState + ) + + state = NewGenesisState(nil, params) + require.Equal(t, &GenesisState{ + Subscriptions: nil, + Params: params, + }, state) + require.Len(t, state.Subscriptions, 0) + + state = NewGenesisState(subscriptions, params) + require.Equal(t, &GenesisState{ + Subscriptions: subscriptions, + Params: params, + }, state) + require.Len(t, state.Subscriptions, 0) + + subscriptions = append(subscriptions, + GenesisSubscription{}, + ) + state = NewGenesisState(subscriptions, params) + require.Equal(t, &GenesisState{ + Subscriptions: subscriptions, + Params: params, + }, state) + require.Len(t, state.Subscriptions, 1) + + subscriptions = append(subscriptions, + GenesisSubscription{}, + ) + state = NewGenesisState(subscriptions, params) + require.Equal(t, &GenesisState{ + Subscriptions: subscriptions, + Params: params, + }, state) + require.Len(t, state.Subscriptions, 2) +} diff --git a/x/subscription/types/keys.go b/x/subscription/types/keys.go index 5e722b31..461076be 100644 --- a/x/subscription/types/keys.go +++ b/x/subscription/types/keys.go @@ -19,6 +19,14 @@ var ( StoreKey = ModuleName ) +var ( + TypeMsgSubscribeToNodeRequest = ModuleName + ":subscribe_to_node" + TypeMsgSubscribeToPlanRequest = ModuleName + ":subscribe_to_plan" + TypeMsgCancelRequest = ModuleName + ":cancel" + TypeMsgAddQuotaRequest = ModuleName + ":add_quota" + TypeMsgUpdateQuotaRequest = ModuleName + ":update_quota" +) + var ( EventModuleName = EventModule{Name: ModuleName} ) diff --git a/x/subscription/types/msg.go b/x/subscription/types/msg.go index 006c2042..5cfa5018 100644 --- a/x/subscription/types/msg.go +++ b/x/subscription/types/msg.go @@ -1,8 +1,6 @@ package types import ( - "fmt" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/errors" @@ -31,22 +29,30 @@ func (m *MsgSubscribeToNodeRequest) Route() string { } func (m *MsgSubscribeToNodeRequest) Type() string { - return fmt.Sprintf("%s:subscribe_to_node", ModuleName) + return TypeMsgSubscribeToNodeRequest } func (m *MsgSubscribeToNodeRequest) ValidateBasic() error { + if m.From == "" { + return errors.Wrap(ErrorInvalidFrom, "from cannot be empty") + } if _, err := sdk.AccAddressFromBech32(m.From); err != nil { - return errors.Wrapf(ErrorInvalidField, "%s", "from") + return errors.Wrapf(ErrorInvalidFrom, "%s", err) + } + if m.Address == "" { + return errors.Wrap(ErrorInvalidAddress, "address cannot be empty") } - - // Address should be valid if _, err := hubtypes.NodeAddressFromBech32(m.Address); err != nil { - return errors.Wrapf(ErrorInvalidField, "%s", "address") + return errors.Wrapf(ErrorInvalidAddress, "%s", err) } - - // Deposit should be valid and positive - if !m.Deposit.IsValid() || !m.Deposit.IsPositive() { - return errors.Wrapf(ErrorInvalidField, "%s", "deposit") + if m.Deposit.IsNegative() { + return errors.Wrap(ErrorInvalidDeposit, "deposit cannot be negative") + } + if m.Deposit.IsZero() { + return errors.Wrap(ErrorInvalidDeposit, "deposit cannot be zero") + } + if !m.Deposit.IsValid() { + return errors.Wrap(ErrorInvalidDeposit, "deposit must be valid") } return nil @@ -78,22 +84,23 @@ func (m *MsgSubscribeToPlanRequest) Route() string { } func (m *MsgSubscribeToPlanRequest) Type() string { - return fmt.Sprintf("%s:subscribe_to_plan", ModuleName) + return TypeMsgSubscribeToPlanRequest } func (m *MsgSubscribeToPlanRequest) ValidateBasic() error { + if m.From == "" { + return errors.Wrap(ErrorInvalidFrom, "from cannot be empty") + } if _, err := sdk.AccAddressFromBech32(m.From); err != nil { - return errors.Wrapf(ErrorInvalidField, "%s", "from") + return errors.Wrapf(ErrorInvalidFrom, "%s", err) } - - // Id shouldn't be zero if m.Id == 0 { - return errors.Wrapf(ErrorInvalidField, "%s", "id") + return errors.Wrap(ErrorInvalidId, "id cannot be zero") } - - // Denom should be valid - if err := sdk.ValidateDenom(m.Denom); err != nil { - return errors.Wrapf(ErrorInvalidField, "%s", "denom") + if m.Denom != "" { + if err := sdk.ValidateDenom(m.Denom); err != nil { + return errors.Wrapf(ErrorInvalidDenom, "%s", err) + } } return nil @@ -124,17 +131,18 @@ func (m *MsgCancelRequest) Route() string { } func (m *MsgCancelRequest) Type() string { - return fmt.Sprintf("%s:cancel", ModuleName) + return TypeMsgCancelRequest } func (m *MsgCancelRequest) ValidateBasic() error { + if m.From == "" { + return errors.Wrap(ErrorInvalidFrom, "from cannot be empty") + } if _, err := sdk.AccAddressFromBech32(m.From); err != nil { - return errors.Wrapf(ErrorInvalidField, "%s", "from") + return errors.Wrapf(ErrorInvalidFrom, "%s", err) } - - // Id shouldn't be zero if m.Id == 0 { - return errors.Wrapf(ErrorInvalidField, "%s", "id") + return errors.Wrap(ErrorInvalidId, "id cannot be zero") } return nil @@ -167,27 +175,27 @@ func (m *MsgAddQuotaRequest) Route() string { } func (m *MsgAddQuotaRequest) Type() string { - return fmt.Sprintf("%s:add_quota", ModuleName) + return TypeMsgAddQuotaRequest } func (m *MsgAddQuotaRequest) ValidateBasic() error { + if m.From == "" { + return errors.Wrap(ErrorInvalidFrom, "from cannot be empty") + } if _, err := sdk.AccAddressFromBech32(m.From); err != nil { - return errors.Wrapf(ErrorInvalidField, "%s", "from") + return errors.Wrapf(ErrorInvalidFrom, "%s", err) } - - // Id shouldn't be zero if m.Id == 0 { - return errors.Wrapf(ErrorInvalidField, "%s", "id") + return errors.Wrap(ErrorInvalidId, "id cannot be zero") + } + if m.Address == "" { + return errors.Wrap(ErrorInvalidAddress, "address cannot be empty") } - - // Address should be valid if _, err := sdk.AccAddressFromBech32(m.Address); err != nil { - return errors.Wrapf(ErrorInvalidField, "%s", "address") + return errors.Wrapf(ErrorInvalidAddress, "%s", err) } - - // Bytes should be positive - if !m.Bytes.IsPositive() { - return errors.Wrapf(ErrorInvalidField, "%s", "bytes") + if m.Bytes.IsNegative() { + return errors.Wrap(ErrorInvalidBytes, "bytes cannot be negative") } return nil @@ -220,27 +228,27 @@ func (m *MsgUpdateQuotaRequest) Route() string { } func (m *MsgUpdateQuotaRequest) Type() string { - return fmt.Sprintf("%s:update_quota", ModuleName) + return TypeMsgUpdateQuotaRequest } func (m *MsgUpdateQuotaRequest) ValidateBasic() error { + if m.From == "" { + return errors.Wrap(ErrorInvalidFrom, "from cannot be empty") + } if _, err := sdk.AccAddressFromBech32(m.From); err != nil { - return errors.Wrapf(ErrorInvalidField, "%s", "from") + return errors.Wrapf(ErrorInvalidFrom, "%s", err) } - - // Id shouldn't be zero if m.Id == 0 { - return errors.Wrapf(ErrorInvalidField, "%s", "id") + return errors.Wrap(ErrorInvalidId, "id cannot be zero") + } + if m.Address == "" { + return errors.Wrap(ErrorInvalidAddress, "address cannot be empty") } - - // Address shouldn be valid if _, err := sdk.AccAddressFromBech32(m.Address); err != nil { - return errors.Wrapf(ErrorInvalidField, "%s", "address") + return errors.Wrapf(ErrorInvalidAddress, "%s", err) } - - // Bytes should be positive - if !m.Bytes.IsPositive() { - return errors.Wrapf(ErrorInvalidField, "%s", "bytes") + if m.Bytes.IsNegative() { + return errors.Wrap(ErrorInvalidBytes, "bytes cannot be negative") } return nil diff --git a/x/subscription/types/msg_test.go b/x/subscription/types/msg_test.go new file mode 100644 index 00000000..610bd391 --- /dev/null +++ b/x/subscription/types/msg_test.go @@ -0,0 +1,713 @@ +package types + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func TestMsgSubscribeToNodeRequest_ValidateBasic(t *testing.T) { + type fields struct { + From string + Address string + Deposit sdk.Coin + } + tests := []struct { + name string + fields fields + wantErr bool + }{ + { + "empty from", + fields{ + From: "", + }, + true, + }, + { + "invalid from", + fields{ + From: "invalid", + }, + true, + }, + { + "invalid prefix from", + fields{ + From: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + }, + true, + }, + { + "10 bytes from", + fields{ + From: "sent1qypqxpq9qcrsszgslawd5s", + }, + true, + }, + { + "20 bytes from", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + }, + true, + }, + { + "30 bytes from", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfqyy3zxfp9ycnjs2fszvfck8", + }, + true, + }, + { + "empty address", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Address: "", + }, + true, + }, + { + "invalid address", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Address: "invalid", + }, + true, + }, + { + "invalid prefix address", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + }, + true, + }, + { + "10 bytes address", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Address: "sentnode1qypqxpq9qcrsszgse4wwrm", + }, + true, + }, + { + "20 bytes address", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Address: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Deposit: sdk.Coin{Amount: sdk.NewInt(0)}, + }, + true, + }, + { + "30 bytes address", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Address: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqyy3zxfp9ycnjs2fsxqglcv", + }, + true, + }, + { + "empty deposit", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Address: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Deposit: sdk.Coin{Amount: sdk.NewInt(0)}, + }, + true, + }, + { + "empty denom deposit", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Address: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Deposit: sdk.Coin{Denom: "", Amount: sdk.NewInt(0)}, + }, + true, + }, + { + "invalid denom deposit", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Address: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Deposit: sdk.Coin{Denom: "o", Amount: sdk.NewInt(1000)}, + }, + true, + }, + { + "negative amount deposit", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Address: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Deposit: sdk.Coin{Denom: "one", Amount: sdk.NewInt(-1000)}, + }, + true, + }, + { + "zero amount deposit", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Address: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Deposit: sdk.Coin{Denom: "one", Amount: sdk.NewInt(0)}, + }, + true, + }, + { + "positive amount deposit", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Address: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Deposit: sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}, + }, + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &MsgSubscribeToNodeRequest{ + From: tt.fields.From, + Address: tt.fields.Address, + Deposit: tt.fields.Deposit, + } + if err := m.ValidateBasic(); (err != nil) != tt.wantErr { + t.Errorf("ValidateBasic() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestMsgSubscribeToPlanRequest_ValidateBasic(t *testing.T) { + type fields struct { + From string + Id uint64 + Denom string + } + tests := []struct { + name string + fields fields + wantErr bool + }{ + { + "empty from", + fields{ + From: "", + }, + true, + }, + { + "invalid from", + fields{ + From: "invalid", + }, + true, + }, + { + "invalid prefix from", + fields{ + From: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + }, + true, + }, + { + "10 bytes from", + fields{ + From: "sent1qypqxpq9qcrsszgslawd5s", + }, + true, + }, + { + "20 bytes from", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + }, + true, + }, + { + "30 bytes from", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfqyy3zxfp9ycnjs2fszvfck8", + }, + true, + }, + { + "zero id", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Id: 0, + }, + true, + }, + { + "positive id", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Id: 1000, + }, + false, + }, + { + "empty denom", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Id: 1000, + Denom: "", + }, + false, + }, + { + "invalid denom", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Id: 1000, + Denom: "o", + }, + true, + }, + { + "one denom", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Id: 1000, + Denom: "one", + }, + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &MsgSubscribeToPlanRequest{ + From: tt.fields.From, + Id: tt.fields.Id, + Denom: tt.fields.Denom, + } + if err := m.ValidateBasic(); (err != nil) != tt.wantErr { + t.Errorf("ValidateBasic() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestMsgCancelRequest_ValidateBasic(t *testing.T) { + type fields struct { + From string + Id uint64 + } + tests := []struct { + name string + fields fields + wantErr bool + }{ + { + "empty from", + fields{ + From: "", + }, + true, + }, + { + "invalid from", + fields{ + From: "invalid", + }, + true, + }, + { + "invalid prefix from", + fields{ + From: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + }, + true, + }, + { + "10 bytes from", + fields{ + From: "sent1qypqxpq9qcrsszgslawd5s", + }, + true, + }, + { + "20 bytes from", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + }, + true, + }, + { + "30 bytes from", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfqyy3zxfp9ycnjs2fszvfck8", + }, + true, + }, + { + "zero id", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Id: 0, + }, + true, + }, + { + "positive id", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Id: 1000, + }, + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &MsgCancelRequest{ + From: tt.fields.From, + Id: tt.fields.Id, + } + if err := m.ValidateBasic(); (err != nil) != tt.wantErr { + t.Errorf("ValidateBasic() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestMsgAddQuotaRequest_ValidateBasic(t *testing.T) { + type fields struct { + From string + Id uint64 + Address string + Bytes sdk.Int + } + tests := []struct { + name string + fields fields + wantErr bool + }{ + { + "empty from", + fields{ + From: "", + }, + true, + }, + { + "invalid from", + fields{ + From: "invalid", + }, + true, + }, + { + "invalid prefix from", + fields{ + From: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + }, + true, + }, + { + "10 bytes from", + fields{ + From: "sent1qypqxpq9qcrsszgslawd5s", + }, + true, + }, + { + "20 bytes from", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + }, + true, + }, + { + "30 bytes from", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfqyy3zxfp9ycnjs2fszvfck8", + }, + true, + }, + { + "zero id", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Id: 0, + }, + true, + }, + { + "positive id", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Id: 1000, + }, + true, + }, + { + "empty address", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Id: 1000, + Address: "", + }, + true, + }, + { + "invalid address", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Id: 1000, + Address: "invalid", + }, + true, + }, + { + "invalid prefix address", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Id: 1000, + Address: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + }, + true, + }, + { + "10 bytes address", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Id: 1000, + Address: "sent1qypqxpq9qcrsszgslawd5s", + }, + true, + }, + { + "20 bytes address", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Id: 1000, + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Bytes: sdk.NewInt(0), + }, + false, + }, + { + "30 bytes address", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Id: 1000, + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfqyy3zxfp9ycnjs2fszvfck8", + }, + true, + }, + { + "negative bytes", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Id: 1000, + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Bytes: sdk.NewInt(-1000), + }, + true, + }, + { + "zero bytes", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Id: 1000, + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Bytes: sdk.NewInt(0), + }, + false, + }, + { + "positive bytes", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Id: 1000, + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Bytes: sdk.NewInt(1000), + }, + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &MsgAddQuotaRequest{ + From: tt.fields.From, + Id: tt.fields.Id, + Address: tt.fields.Address, + Bytes: tt.fields.Bytes, + } + if err := m.ValidateBasic(); (err != nil) != tt.wantErr { + t.Errorf("ValidateBasic() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestMsgUpdateQuotaRequest_ValidateBasic(t *testing.T) { + type fields struct { + From string + Id uint64 + Address string + Bytes sdk.Int + } + tests := []struct { + name string + fields fields + wantErr bool + }{ + { + "empty from", + fields{ + From: "", + }, + true, + }, + { + "invalid from", + fields{ + From: "invalid", + }, + true, + }, + { + "invalid prefix from", + fields{ + From: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + }, + true, + }, + { + "10 bytes from", + fields{ + From: "sent1qypqxpq9qcrsszgslawd5s", + }, + true, + }, + { + "20 bytes from", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + }, + true, + }, + { + "30 bytes from", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfqyy3zxfp9ycnjs2fszvfck8", + }, + true, + }, + { + "zero id", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Id: 0, + }, + true, + }, + { + "positive id", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Id: 1000, + }, + true, + }, + { + "empty address", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Id: 1000, + Address: "", + }, + true, + }, + { + "invalid address", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Id: 1000, + Address: "invalid", + }, + true, + }, + { + "invalid prefix address", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Id: 1000, + Address: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + }, + true, + }, + { + "10 bytes address", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Id: 1000, + Address: "sent1qypqxpq9qcrsszgslawd5s", + }, + true, + }, + { + "20 bytes address", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Id: 1000, + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Bytes: sdk.NewInt(0), + }, + false, + }, + { + "30 bytes address", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Id: 1000, + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfqyy3zxfp9ycnjs2fszvfck8", + }, + true, + }, + { + "negative bytes", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Id: 1000, + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Bytes: sdk.NewInt(-1000), + }, + true, + }, + { + "zero bytes", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Id: 1000, + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Bytes: sdk.NewInt(0), + }, + false, + }, + { + "positive bytes", + fields{ + From: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Id: 1000, + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Bytes: sdk.NewInt(1000), + }, + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &MsgUpdateQuotaRequest{ + From: tt.fields.From, + Id: tt.fields.Id, + Address: tt.fields.Address, + Bytes: tt.fields.Bytes, + } + if err := m.ValidateBasic(); (err != nil) != tt.wantErr { + t.Errorf("ValidateBasic() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/x/subscription/types/params.go b/x/subscription/types/params.go index 586bca23..a9b4e761 100644 --- a/x/subscription/types/params.go +++ b/x/subscription/types/params.go @@ -19,27 +19,33 @@ var ( _ params.ParamSet = (*Params)(nil) ) -func (p *Params) Validate() error { - if p.InactiveDuration <= 0 { - return fmt.Errorf("inactive_duration should be positive") +func (m *Params) Validate() error { + if m.InactiveDuration < 0 { + return fmt.Errorf("inactive_duration cannot be negative") + } + if m.InactiveDuration == 0 { + return fmt.Errorf("inactive_duration cannot be zero") } return nil } -func (p *Params) ParamSetPairs() params.ParamSetPairs { +func (m *Params) ParamSetPairs() params.ParamSetPairs { return params.ParamSetPairs{ { Key: KeyInactiveDuration, - Value: &p.InactiveDuration, + Value: &m.InactiveDuration, ValidatorFn: func(v interface{}) error { value, ok := v.(time.Duration) if !ok { return fmt.Errorf("invalid parameter type %T", v) } - if value <= 0 { - return fmt.Errorf("inactive duration value should be positive") + if value < 0 { + return fmt.Errorf("value cannot be negative") + } + if value == 0 { + return fmt.Errorf("value cannot be zero") } return nil diff --git a/x/subscription/types/params_test.go b/x/subscription/types/params_test.go new file mode 100644 index 00000000..866328d9 --- /dev/null +++ b/x/subscription/types/params_test.go @@ -0,0 +1,49 @@ +package types + +import ( + "testing" + "time" +) + +func TestParams_Validate(t *testing.T) { + type fields struct { + InactiveDuration time.Duration + } + tests := []struct { + name string + fields fields + wantErr bool + }{ + { + "negative inactive duration", + fields{ + InactiveDuration: -1000, + }, + true, + }, + { + "zero inactive duration", + fields{ + InactiveDuration: 0, + }, + true, + }, + { + "positive inactive duration", + fields{ + InactiveDuration: 1000, + }, + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &Params{ + InactiveDuration: tt.fields.InactiveDuration, + } + if err := m.Validate(); (err != nil) != tt.wantErr { + t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/x/subscription/types/quota.go b/x/subscription/types/quota.go index 841d0592..5e57cf01 100644 --- a/x/subscription/types/quota.go +++ b/x/subscription/types/quota.go @@ -4,14 +4,15 @@ import ( "fmt" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/errors" ) -func (q *Quota) GetAddress() sdk.AccAddress { - if q.Address == "" { +func (m *Quota) GetAddress() sdk.AccAddress { + if m.Address == "" { return nil } - address, err := sdk.AccAddressFromBech32(q.Address) + address, err := sdk.AccAddressFromBech32(m.Address) if err != nil { panic(err) } @@ -19,21 +20,26 @@ func (q *Quota) GetAddress() sdk.AccAddress { return address } -func (q *Quota) Validate() error { - if _, err := sdk.AccAddressFromBech32(q.Address); err != nil { - return fmt.Errorf("address should not be nil or empty") +func (m *Quota) Validate() error { + if m.Address == "" { + return fmt.Errorf("address cannot be empty") } - if q.Consumed.IsNegative() { - return fmt.Errorf("consumed should not be negative") + if _, err := sdk.AccAddressFromBech32(m.Address); err != nil { + return errors.Wrapf(err, "invalid address %s", m.Address) } - if q.Allocated.IsNegative() { - return fmt.Errorf("allocated should not be negative") + if m.Allocated.IsNegative() { + return fmt.Errorf("allocated cannot be negative") } - if q.Consumed.GT(q.Allocated) { - return fmt.Errorf("consumed should not be greater than allocated") + if m.Consumed.IsNegative() { + return fmt.Errorf("consumed cannot be negative") + } + if m.Consumed.GT(m.Allocated) { + return fmt.Errorf("consumed cannot be greater than allocated") } return nil } -type Quotas []Quota +type ( + Quotas []Quota +) diff --git a/x/subscription/types/quota_test.go b/x/subscription/types/quota_test.go new file mode 100644 index 00000000..26569dde --- /dev/null +++ b/x/subscription/types/quota_test.go @@ -0,0 +1,198 @@ +package types + +import ( + "reflect" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func TestQuota_GetAddress(t *testing.T) { + type fields struct { + Address string + Allocated sdk.Int + Consumed sdk.Int + } + tests := []struct { + name string + fields fields + want sdk.AccAddress + }{ + { + "empty", + fields{ + Address: "", + }, + nil, + }, + { + "20 bytes", + fields{ + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + }, + sdk.AccAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &Quota{ + Address: tt.fields.Address, + Allocated: tt.fields.Allocated, + Consumed: tt.fields.Consumed, + } + if got := m.GetAddress(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("GetAddress() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestQuota_Validate(t *testing.T) { + type fields struct { + Address string + Allocated sdk.Int + Consumed sdk.Int + } + tests := []struct { + name string + fields fields + wantErr bool + }{ + { + "empty address", + fields{ + Address: "", + }, + true, + }, + { + "invalid address", + fields{ + Address: "invalid", + }, + true, + }, + { + "invalid prefix address", + fields{ + Address: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + }, + true, + }, + { + "10 bytes address", + fields{ + Address: "sent1qypqxpq9qcrsszgslawd5s", + }, + true, + }, + { + "20 bytes address", + fields{ + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Allocated: sdk.NewInt(0), + Consumed: sdk.NewInt(0), + }, + false, + }, + { + "30 bytes address", + fields{ + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfqyy3zxfp9ycnjs2fszvfck8", + }, + true, + }, + { + "negative allocated", + fields{ + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Allocated: sdk.NewInt(-1000), + }, + true, + }, + { + "zero allocated", + fields{ + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Allocated: sdk.NewInt(0), + Consumed: sdk.NewInt(0), + }, + false, + }, + { + "positive allocated", + fields{ + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Allocated: sdk.NewInt(1000), + Consumed: sdk.NewInt(0), + }, + false, + }, + { + "negative consumed", + fields{ + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Allocated: sdk.NewInt(1000), + Consumed: sdk.NewInt(-1000), + }, + true, + }, + { + "zero consumed", + fields{ + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Allocated: sdk.NewInt(1000), + Consumed: sdk.NewInt(0), + }, + false, + }, + { + "positive consumed", + fields{ + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Allocated: sdk.NewInt(1000), + Consumed: sdk.NewInt(1000), + }, + false, + }, + { + "allocated less than consumed", + fields{ + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Allocated: sdk.NewInt(1000), + Consumed: sdk.NewInt(2000), + }, + true, + }, + { + "allocated equals to consumed", + fields{ + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Allocated: sdk.NewInt(2000), + Consumed: sdk.NewInt(2000), + }, + false, + }, + { + "allocated greater than consumed", + fields{ + Address: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Allocated: sdk.NewInt(2000), + Consumed: sdk.NewInt(1000), + }, + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &Quota{ + Address: tt.fields.Address, + Allocated: tt.fields.Allocated, + Consumed: tt.fields.Consumed, + } + if err := m.Validate(); (err != nil) != tt.wantErr { + t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/x/subscription/types/subscription.go b/x/subscription/types/subscription.go index 969c4594..758cebf9 100644 --- a/x/subscription/types/subscription.go +++ b/x/subscription/types/subscription.go @@ -4,16 +4,17 @@ import ( "fmt" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/errors" hubtypes "github.com/sentinel-official/hub/types" ) -func (s *Subscription) GetNode() hubtypes.NodeAddress { - if s.Node == "" { +func (m *Subscription) GetNode() hubtypes.NodeAddress { + if m.Node == "" { return nil } - address, err := hubtypes.NodeAddressFromBech32(s.Node) + address, err := hubtypes.NodeAddressFromBech32(m.Node) if err != nil { panic(err) } @@ -21,12 +22,12 @@ func (s *Subscription) GetNode() hubtypes.NodeAddress { return address } -func (s *Subscription) GetOwner() sdk.AccAddress { - if s.Owner == "" { +func (m *Subscription) GetOwner() sdk.AccAddress { + if m.Owner == "" { return nil } - address, err := sdk.AccAddressFromBech32(s.Owner) + address, err := sdk.AccAddressFromBech32(m.Owner) if err != nil { panic(err) } @@ -34,10 +35,10 @@ func (s *Subscription) GetOwner() sdk.AccAddress { return address } -func (s *Subscription) Amount(consumed sdk.Int) sdk.Coin { +func (m *Subscription) Amount(consumed sdk.Int) sdk.Coin { var ( amount sdk.Int - x = hubtypes.Gigabyte.Quo(s.Price.Amount) + x = hubtypes.Gigabyte.Quo(m.Price.Amount) ) if x.IsPositive() { @@ -45,50 +46,71 @@ func (s *Subscription) Amount(consumed sdk.Int) sdk.Coin { CeilTo(x). Sum().Quo(x) } else { - y := sdk.NewDecFromInt(s.Price.Amount). + y := sdk.NewDecFromInt(m.Price.Amount). QuoInt(hubtypes.Gigabyte). Ceil().TruncateInt() amount = consumed.Mul(y) } - return sdk.NewCoin(s.Price.Denom, amount) + return sdk.NewCoin(m.Price.Denom, amount) } -func (s *Subscription) Validate() error { - if s.Id == 0 { - return fmt.Errorf("id should not be zero") +func (m *Subscription) Validate() error { + if m.Id == 0 { + return fmt.Errorf("id cannot be zero") } - if _, err := sdk.AccAddressFromBech32(s.Owner); err != nil { - return fmt.Errorf("owner should not nil or empty") + if m.Owner == "" { + return fmt.Errorf("owner cannot be empty") } - - if s.Plan == 0 { - if _, err := hubtypes.NodeAddressFromBech32(s.Node); err != nil { - return fmt.Errorf("node should not be nil or empty") + if _, err := sdk.AccAddressFromBech32(m.Owner); err != nil { + return errors.Wrapf(err, "invalid owner %s", m.Owner) + } + if m.Node == "" && m.Plan == 0 { + return fmt.Errorf("both node and plan cannot be empty") + } + if m.Node != "" && m.Plan != 0 { + return fmt.Errorf("either node or plan must be empty") + } + if m.Node != "" { + if _, err := hubtypes.NodeAddressFromBech32(m.Node); err != nil { + return errors.Wrapf(err, "invalid node %s", m.Node) } - if !s.Price.IsValid() { - return fmt.Errorf("price should be valid") + if m.Price.IsZero() { + return fmt.Errorf("price cannot be zero") } - if !s.Deposit.IsValid() { - return fmt.Errorf("deposit should be valid") + if !m.Price.IsValid() { + return fmt.Errorf("price must be valid") } - } else { - if s.Expiry.IsZero() { - return fmt.Errorf("expiry should not be zero") + if m.Deposit.IsZero() { + return fmt.Errorf("deposit cannot be zero") + } + if !m.Deposit.IsValid() { + return fmt.Errorf("deposit must be valid") } } - - if s.Free.IsNegative() { - return fmt.Errorf("free should not be negative") + if m.Plan != 0 { + if m.Denom != "" { + if err := sdk.ValidateDenom(m.Denom); err != nil { + return errors.Wrapf(err, "invalid denom %s", m.Denom) + } + } + if m.Expiry.IsZero() { + return fmt.Errorf("expiry cannot be zero") + } } - if !s.Status.IsValid() { - return fmt.Errorf("status should be valid") + if m.Free.IsNegative() { + return fmt.Errorf("free cannot not be negative") } - if s.StatusAt.IsZero() { - return fmt.Errorf("status_at should not be zero") + if !m.Status.IsValid() { + return fmt.Errorf("status must be valid") + } + if m.StatusAt.IsZero() { + return fmt.Errorf("status_at cannot be zero") } return nil } -type Subscriptions []Subscription +type ( + Subscriptions []Subscription +) diff --git a/x/subscription/types/subscription_test.go b/x/subscription/types/subscription_test.go new file mode 100644 index 00000000..e08682cf --- /dev/null +++ b/x/subscription/types/subscription_test.go @@ -0,0 +1,564 @@ +package types + +import ( + "reflect" + "testing" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + + hubtypes "github.com/sentinel-official/hub/types" +) + +func TestSubscription_GetNode(t *testing.T) { + type fields struct { + Node string + } + tests := []struct { + name string + fields fields + want hubtypes.NodeAddress + }{ + { + "empty", + fields{ + Node: "", + }, + nil, + }, + { + "20 bytes", + fields{ + Node: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + }, + hubtypes.NodeAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &Subscription{ + Node: tt.fields.Node, + } + if got := m.GetNode(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("GetNode() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSubscription_GetOwner(t *testing.T) { + type fields struct { + Owner string + } + tests := []struct { + name string + fields fields + want sdk.AccAddress + }{ + { + "empty", + fields{ + Owner: "", + }, + nil, + }, + { + "20 bytes", + fields{ + Owner: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + }, + sdk.AccAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &Subscription{ + Owner: tt.fields.Owner, + } + if got := m.GetOwner(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("GetOwner() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSubscription_Validate(t *testing.T) { + type fields struct { + Id uint64 + Owner string + Node string + Price sdk.Coin + Deposit sdk.Coin + Plan uint64 + Denom string + Expiry time.Time + Free sdk.Int + Status hubtypes.Status + StatusAt time.Time + } + tests := []struct { + name string + fields fields + wantErr bool + }{ + { + "zero id", + fields{ + Id: 0, + }, + true, + }, + { + "positive id", + fields{ + Id: 1000, + }, + true, + }, + { + "empty owner", + fields{ + Id: 1000, + Owner: "", + }, + true, + }, + { + "invalid owner", + fields{ + Id: 1000, + Owner: "invalid", + }, + true, + }, + { + "invalid prefix owner", + fields{ + Id: 1000, + Owner: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + }, + true, + }, + { + "10 bytes owner", + fields{ + Id: 1000, + Owner: "sent1qypqxpq9qcrsszgslawd5s", + }, + true, + }, + { + "20 bytes owner", + fields{ + Id: 1000, + Owner: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + }, + true, + }, + { + "30 bytes owner", + fields{ + Id: 1000, + Owner: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfqyy3zxfp9ycnjs2fszvfck8", + }, + true, + }, + { + "empty node", + fields{ + Id: 1000, + Owner: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Node: "", + }, + true, + }, + { + "invalid node", + fields{ + Id: 1000, + Owner: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Node: "invalid", + }, + true, + }, + { + "invalid prefix node", + fields{ + Id: 1000, + Owner: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Node: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + }, + true, + }, + { + "10 bytes node", + fields{ + Id: 1000, + Owner: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Node: "sentnode1qypqxpq9qcrsszgse4wwrm", + }, + true, + }, + { + "20 bytes node", + fields{ + Id: 1000, + Owner: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Node: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Price: sdk.Coin{Amount: sdk.NewInt(0)}, + }, + true, + }, + { + "30 bytes node", + fields{ + Id: 1000, + Owner: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Node: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqyy3zxfp9ycnjs2fsxqglcv", + }, + true, + }, + { + "empty price", + fields{ + Id: 1000, + Owner: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Node: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Price: sdk.Coin{Amount: sdk.NewInt(0)}, + }, + true, + }, + { + "empty denom price", + fields{ + Id: 1000, + Owner: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Node: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Price: sdk.Coin{Denom: "", Amount: sdk.NewInt(0)}, + }, + true, + }, + { + "invalid denom price", + fields{ + Id: 1000, + Owner: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Node: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Price: sdk.Coin{Denom: "o", Amount: sdk.NewInt(0)}, + }, + true, + }, + { + "negative amount price", + fields{ + Id: 1000, + Owner: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Node: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Price: sdk.Coin{Denom: "one", Amount: sdk.NewInt(-1000)}, + }, + true, + }, + { + "zero amount price", + fields{ + Id: 1000, + Owner: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Node: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Price: sdk.Coin{Denom: "one", Amount: sdk.NewInt(0)}, + }, + true, + }, + { + "positive amount price", + fields{ + Id: 1000, + Owner: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Node: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Price: sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}, + Deposit: sdk.Coin{Amount: sdk.NewInt(0)}, + }, + true, + }, + { + "empty deposit", + fields{ + Id: 1000, + Owner: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Node: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Price: sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}, + Deposit: sdk.Coin{Amount: sdk.NewInt(0)}, + }, + true, + }, + { + "empty denom deposit", + fields{ + Id: 1000, + Owner: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Node: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Price: sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}, + Deposit: sdk.Coin{Denom: "", Amount: sdk.NewInt(0)}, + }, + true, + }, + { + "invalid denom deposit", + fields{ + Id: 1000, + Owner: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Node: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Price: sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}, + Deposit: sdk.Coin{Denom: "o", Amount: sdk.NewInt(0)}, + }, + true, + }, + { + "negative amount deposit", + fields{ + Id: 1000, + Owner: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Node: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Price: sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}, + Deposit: sdk.Coin{Denom: "one", Amount: sdk.NewInt(-1000)}, + }, + true, + }, + { + "zero amount deposit", + fields{ + Id: 1000, + Owner: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Node: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Price: sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}, + Deposit: sdk.Coin{Denom: "one", Amount: sdk.NewInt(0)}, + }, + true, + }, + { + "positive amount deposit", + fields{ + Id: 1000, + Owner: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Node: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Price: sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}, + Deposit: sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}, + Free: sdk.NewInt(0), + }, + true, + }, + { + "zero plan", + fields{ + Id: 1000, + Owner: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Node: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Price: sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}, + Deposit: sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}, + Plan: 0, + Free: sdk.NewInt(0), + }, + true, + }, + { + "positive plan", + fields{ + Id: 1000, + Owner: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Node: "sentnode1qypqxpq9qcrsszgszyfpx9q4zct3sxfqelr5ey", + Price: sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}, + Deposit: sdk.Coin{Denom: "one", Amount: sdk.NewInt(1000)}, + Plan: 1000, + }, + true, + }, + { + "empty denom", + fields{ + Id: 1000, + Owner: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Plan: 1000, + Denom: "", + }, + true, + }, + { + "invalid denom", + fields{ + Id: 1000, + Owner: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Plan: 1000, + Denom: "o", + }, + true, + }, + { + "one denom", + fields{ + Id: 1000, + Owner: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Plan: 1000, + Denom: "one", + }, + true, + }, + { + "zero expiry", + fields{ + Id: 1000, + Owner: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Plan: 1000, + Denom: "one", + Expiry: time.Time{}, + }, + true, + }, + { + "now expiry", + fields{ + Id: 1000, + Owner: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Plan: 1000, + Denom: "one", + Expiry: time.Now(), + Free: sdk.NewInt(0), + }, + true, + }, + { + "negative free", + fields{ + Id: 1000, + Owner: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Plan: 1000, + Denom: "one", + Expiry: time.Now(), + Free: sdk.NewInt(-1000), + }, + true, + }, + { + "zero free", + fields{ + Id: 1000, + Owner: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Plan: 1000, + Denom: "one", + Expiry: time.Now(), + Free: sdk.NewInt(0), + }, + true, + }, + { + "positive free", + fields{ + Id: 1000, + Owner: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Plan: 1000, + Denom: "one", + Expiry: time.Now(), + Free: sdk.NewInt(1000), + }, + true, + }, + { + "unknown status", + fields{ + Id: 1000, + Owner: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Plan: 1000, + Denom: "one", + Expiry: time.Now(), + Free: sdk.NewInt(1000), + Status: hubtypes.StatusUnknown, + }, + true, + }, + { + "inactive status", + fields{ + Id: 1000, + Owner: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Plan: 1000, + Denom: "one", + Expiry: time.Now(), + Free: sdk.NewInt(1000), + Status: hubtypes.StatusInactive, + }, + true, + }, + { + "inactive pending status", + fields{ + Id: 1000, + Owner: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Plan: 1000, + Denom: "one", + Expiry: time.Now(), + Free: sdk.NewInt(1000), + Status: hubtypes.StatusInactivePending, + }, + true, + }, + { + "active status", + fields{ + Id: 1000, + Owner: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Plan: 1000, + Denom: "one", + Expiry: time.Now(), + Free: sdk.NewInt(1000), + Status: hubtypes.StatusActive, + }, + true, + }, + { + "zero status_at", + fields{ + Id: 1000, + Owner: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Plan: 1000, + Denom: "one", + Expiry: time.Now(), + Free: sdk.NewInt(1000), + Status: hubtypes.StatusActive, + StatusAt: time.Time{}, + }, + true, + }, + { + "now status_at", + fields{ + Id: 1000, + Owner: "sent1qypqxpq9qcrsszgszyfpx9q4zct3sxfq0fzduj", + Plan: 1000, + Denom: "one", + Expiry: time.Now(), + Free: sdk.NewInt(1000), + Status: hubtypes.StatusActive, + StatusAt: time.Now(), + }, + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &Subscription{ + Id: tt.fields.Id, + Owner: tt.fields.Owner, + Node: tt.fields.Node, + Price: tt.fields.Price, + Deposit: tt.fields.Deposit, + Plan: tt.fields.Plan, + Denom: tt.fields.Denom, + Expiry: tt.fields.Expiry, + Free: tt.fields.Free, + Status: tt.fields.Status, + StatusAt: tt.fields.StatusAt, + } + if err := m.Validate(); (err != nil) != tt.wantErr { + t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/x/swap/module.go b/x/swap/module.go index 8caaaf8a..dd11a63b 100644 --- a/x/swap/module.go +++ b/x/swap/module.go @@ -118,8 +118,6 @@ func (a AppModule) EndBlock(_ sdk.Context, _ abcitypes.RequestEndBlock) []abcity return nil } -// AppSimulaion Methods - func (AppModule) GenerateGenesisState(simState *module.SimulationState) { simulation.RandomizedGenesisState(simState) } @@ -133,9 +131,9 @@ func (a AppModule) RandomizedParams(r *rand.Rand) []sdksimulation.ParamChange { } func (a AppModule) RegisterStoreDecoder(sdr sdk.StoreDecoderRegistry) { - sdr[types.StoreKey] = simulation.NewDecodeStore(a.cdc) + sdr[types.StoreKey] = simulation.NewStoreDecoder(a.cdc) } -func (a AppModule) WeightedOperations(simState module.SimulationState) []sdksimulation.WeightedOperation { - return simulation.WeightedOperations(simState.AppParams, simState.Cdc, a.k) +func (a AppModule) WeightedOperations(_ module.SimulationState) []sdksimulation.WeightedOperation { + return nil } diff --git a/x/swap/simulation/decoder.go b/x/swap/simulation/decoder.go index 6c4e86cb..701a18dd 100644 --- a/x/swap/simulation/decoder.go +++ b/x/swap/simulation/decoder.go @@ -3,22 +3,23 @@ package simulation import ( "bytes" "fmt" + "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/types/kv" + "github.com/sentinel-official/hub/x/swap/types" ) -func NewDecodeStore(cdc codec.Marshaler) func(kvA, kvB kv.Pair) string { - decoderFn := func(kvA, kvB kv.Pair) string { +func NewStoreDecoder(cdc codec.Marshaler) func(kvA, kvB kv.Pair) string { + return func(kvA, kvB kv.Pair) string { if bytes.Equal(kvA.Key[:1], types.SwapKeyPrefix) { var swapA, swapB types.MsgSwapRequest cdc.MustUnmarshalBinaryBare(kvA.Value, &swapA) cdc.MustUnmarshalBinaryBare(kvB.Value, &swapB) - return fmt.Sprintf("%s\n%s", swapA, swapB) + + return fmt.Sprintf("%v\n%v", swapA, swapB) } - panic(fmt.Sprintf("invalid swap key prefix: %X", kvA.Key[:1])) + panic(fmt.Sprintf("invalid key prefix %X", kvA.Key[:1])) } - - return decoderFn } diff --git a/x/swap/simulation/decoder_test.go b/x/swap/simulation/decoder_test.go deleted file mode 100644 index 78245cef..00000000 --- a/x/swap/simulation/decoder_test.go +++ /dev/null @@ -1,44 +0,0 @@ -package simulation - -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" - "github.com/cosmos/cosmos-sdk/simapp" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/kv" - "github.com/sentinel-official/hub/x/swap/types" -) - -func TestDecoderStore(t *testing.T) { - cdc, _ := simapp.MakeCodecs() - dec := NewDecodeStore(cdc) - swapperPK := ed25519.GenPrivKey().PubKey() - swapperAddr := sdk.AccAddress(swapperPK.Address()) - receiver := sdk.AccAddress(swapperPK.Address()) - txHash := types.EthereumHash{} - txHash.SetBytes([]byte("6ca7abe5be0ee9bacf582a42f70a2c384b2121ffa6cc33f3ae0b7e41d3480dbe")) - amount := sdk.NewInt(1000) - - swap := types.NewMsgSwapRequest(swapperAddr, txHash, receiver, amount) - - KVPair := kv.Pair{ - Key: types.SwapKeyPrefix, Value: cdc.MustMarshalBinaryBare(swap), - } - - tests := []struct { - name string - want string - }{ - {"SwapMessage", fmt.Sprintf("%s\n%s", *swap, *swap)}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - require.Equal(t, tt.want, dec(KVPair, KVPair), tt.name) - }) - } -} diff --git a/x/swap/simulation/genesis.go b/x/swap/simulation/genesis.go index 1050624b..0f73722e 100644 --- a/x/swap/simulation/genesis.go +++ b/x/swap/simulation/genesis.go @@ -1,74 +1,27 @@ package simulation import ( - "encoding/json" - "fmt" - "math/rand" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/sentinel-official/hub/x/swap/types" ) -func GetRandomSwapDenom(r *rand.Rand) string { - // denom length should be between 3-128 - return simulation.RandStringOfLength(r, r.Intn(125)+3) -} - -func GetRandomApproveBy(r *rand.Rand) string { - bz := make([]byte, 20) - _, err := r.Read(bz) - if err != nil { - panic(err) - } - - return sdk.AccAddress(bz).String() -} - -func GetRandomSwapEnabled(r *rand.Rand) bool { - return r.Intn(2) == 1 -} - -func RandomizedGenesisState(simState *module.SimulationState) { +func RandomizedGenesisState(state *module.SimulationState) { var ( - swapEnabled bool - approveBy string - swapDenom string + account, _ = simulation.RandomAcc(state.Rand, state.Accounts) + params = types.NewParams( + true, + sdk.DefaultBondDenom, + account.Address.String(), + ) ) - swapEnabledSim := func(r *rand.Rand) { - swapEnabled = GetRandomSwapEnabled(r) - } - - approveBySim := func(r *rand.Rand) { - approveBy = GetRandomApproveBy(r) - } - - swapDenomSim := func(r *rand.Rand) { - swapDenom = GetRandomSwapDenom(r) - } - - simState.AppParams.GetOrGenerate(simState.Cdc, string(types.KeySwapEnabled), &swapEnabled, simState.Rand, swapEnabledSim) - simState.AppParams.GetOrGenerate(simState.Cdc, string(types.KeyApproveBy), &approveBy, simState.Rand, approveBySim) - simState.AppParams.GetOrGenerate(simState.Cdc, string(types.KeySwapDenom), &swapDenom, simState.Rand, swapDenomSim) - - params := types.NewParams(swapEnabled, swapDenom, approveBy) - - var swaps types.Swaps - - swaps = append(swaps, types.Swap{ - TxHash: []byte("b37c4b36298f20ac7fc5de2b4ac1167d7a044402a40f2b5f1e1bb7f0ee5f6346"), - Receiver: "sent1grdunxx5jxd0ja75wt508sn6v39p70hhw53zs8", - Amount: sdk.Coin{Denom: "sent", Amount: sdk.NewInt(1000)}, - }) - - swapGenesis := types.NewGenesisState(swaps, params) - bz, err := json.MarshalIndent(&swapGenesis.Params, "", "") - if err != nil { - panic(err) - } - - fmt.Printf("selected randomly generated swap parameters: %s\n", bz) - simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(swapGenesis) + state.GenState[types.ModuleName] = state.Cdc.MustMarshalJSON( + types.NewGenesisState( + nil, + params, + ), + ) } diff --git a/x/swap/simulation/genesis_test.go b/x/swap/simulation/genesis_test.go deleted file mode 100644 index 9075a8ef..00000000 --- a/x/swap/simulation/genesis_test.go +++ /dev/null @@ -1,57 +0,0 @@ -package simulation - -import ( - "encoding/json" - "math/rand" - "testing" - "time" - - "github.com/cosmos/cosmos-sdk/codec" - cdctypes "github.com/cosmos/cosmos-sdk/codec/types" - cryptocdc "github.com/cosmos/cosmos-sdk/crypto/codec" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/module" - sdksimulation "github.com/cosmos/cosmos-sdk/types/simulation" - "github.com/sentinel-official/hub/x/swap/types" - "github.com/stretchr/testify/require" -) - -func TestRandomizedGenesisState(t *testing.T) { - interfaceRegistry := cdctypes.NewInterfaceRegistry() - cryptocdc.RegisterInterfaces(interfaceRegistry) - - cdc := codec.NewProtoCodec(interfaceRegistry) - - s := rand.NewSource(1) - r := rand.New(s) - - simState := &module.SimulationState{ - AppParams: make(sdksimulation.AppParams), - Cdc: cdc, - Rand: r, - GenState: make(map[string]json.RawMessage), - Accounts: sdksimulation.RandomAccounts(r, 3), - InitialStake: 1000, - NumBonded: 0, - GenTimestamp: time.Time{}, - UnbondTime: 0, - ParamChanges: []sdksimulation.ParamChange{}, - Contents: []sdksimulation.WeightedProposalContent{}, - } - - RandomizedGenesisState(simState) - - var swapGenesis types.GenesisState - simState.Cdc.MustUnmarshalJSON(simState.GenState[types.ModuleName], &swapGenesis) - - require.Equal(t, "sent1grdunxx5jxd0ja75wt508sn6v39p70hhw53zs8", swapGenesis.Swaps[0].Receiver) - require.Equal(t, []byte{ - 0x62, 0x33, 0x37, 0x63, 0x34, 0x62, 0x33, 0x36, 0x32, 0x39, 0x38, 0x66, 0x32, 0x30, 0x61, 0x63, 0x37, 0x66, - 0x63, 0x35, 0x64, 0x65, 0x32, 0x62, 0x34, 0x61, 0x63, 0x31, 0x31, 0x36, 0x37, 0x64, 0x37, 0x61, 0x30, 0x34, - 0x34, 0x34, 0x30, 0x32, 0x61, 0x34, 0x30, 0x66, 0x32, 0x62, 0x35, 0x66, 0x31, 0x65, 0x31, 0x62, 0x62, 0x37, - 0x66, 0x30, 0x65, 0x65, 0x35, 0x66, 0x36, 0x33, 0x34, 0x36, - }, swapGenesis.Swaps[0].TxHash) - require.Equal(t, sdk.Coin{Denom: "sent", Amount: sdk.NewInt(1000)}, swapGenesis.Swaps[0].Amount) - require.Equal(t, false, swapGenesis.Params.SwapEnabled) - require.Equal(t, "cxgdXhhuTSkuxK", swapGenesis.Params.SwapDenom) -} diff --git a/x/swap/simulation/msg.go b/x/swap/simulation/msg.go deleted file mode 100644 index 02eddda2..00000000 --- a/x/swap/simulation/msg.go +++ /dev/null @@ -1 +0,0 @@ -package simulation diff --git a/x/swap/simulation/operations.go b/x/swap/simulation/operations.go deleted file mode 100644 index 24c7e229..00000000 --- a/x/swap/simulation/operations.go +++ /dev/null @@ -1,86 +0,0 @@ -package simulation - -import ( - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/simapp/helpers" - "github.com/cosmos/cosmos-sdk/simapp/params" - sdksimulation "github.com/cosmos/cosmos-sdk/types/simulation" - "github.com/cosmos/cosmos-sdk/x/simulation" - "github.com/sentinel-official/hub/x/swap/keeper" - types "github.com/sentinel-official/hub/x/swap/types" - - "math/rand" - - "github.com/cosmos/cosmos-sdk/baseapp" - sdk "github.com/cosmos/cosmos-sdk/types" -) - -const OpWeightMsgSwapRequest = "op_weight_msg_swap_request" - -func WeightedOperations(ap sdksimulation.AppParams, cdc codec.JSONMarshaler, k keeper.Keeper) simulation.WeightedOperations { - var weightMsgSwapRequest int - - randSwapFn := func(r *rand.Rand) { - weightMsgSwapRequest = 100 - } - ap.GetOrGenerate(cdc, OpWeightMsgSwapRequest, &weightMsgSwapRequest, nil, randSwapFn) - - operation := simulation.NewWeightedOperation(weightMsgSwapRequest, SimulateMsgSwapRequest(k, cdc)) - return simulation.WeightedOperations{operation} -} - -func SimulateMsgSwapRequest(k keeper.Keeper, cdc codec.JSONMarshaler) sdksimulation.Operation { - return func( - r *rand.Rand, - app *baseapp.BaseApp, - ctx sdk.Context, - accounts []sdksimulation.Account, - chainID string, - ) (sdksimulation.OperationMsg, []sdksimulation.FutureOperation, error) { - acc1, _ := sdksimulation.RandomAcc(r, accounts) - acc2, _ := sdksimulation.RandomAcc(r, accounts) - sender := k.GetAccount(ctx, acc1.Address) - receiver := k.GetAccount(ctx, acc2.Address) - hash := types.EthereumHash{} - - denom := k.GetParams(ctx).SwapDenom - amount := sdksimulation.RandomAmount(r, sdk.NewInt(60<<13)) - - coins := sdk.Coins{ - {Denom: denom, Amount: amount}, - } - - fees, err := sdksimulation.RandomFees(r, ctx, coins) - if err != nil { - return sdksimulation.NoOpMsg(types.ModuleName, "swap_request", err.Error()), nil, err - } - - _, found := k.GetSwap(ctx, hash) - if found { - return sdksimulation.NoOpMsg(types.ModuleName, "swap_request", "swap already exists for this txn hash"), nil, nil - } - - msg := types.NewMsgSwapRequest(sender.GetAddress(), hash, receiver.GetAddress(), amount) - txGen := params.MakeTestEncodingConfig().TxConfig - - txn, err := helpers.GenTx( - txGen, - []sdk.Msg{msg}, - fees, - helpers.DefaultGenTxGas, - chainID, - []uint64{sender.GetAccountNumber()}, - []uint64{sender.GetSequence()}, - ) - if err != nil { - return sdksimulation.NoOpMsg(types.ModuleName, "swap_request", err.Error()), nil, err - } - - _, _, err = app.Deliver(txGen.TxEncoder(), txn) - if err != nil { - return sdksimulation.NoOpMsg(types.ModuleName, "swap_request", err.Error()), nil, err - } - - return sdksimulation.NewOperationMsg(msg, true, ""), nil, nil - } -} diff --git a/x/swap/simulation/operations_test.go b/x/swap/simulation/operations_test.go deleted file mode 100644 index 02eddda2..00000000 --- a/x/swap/simulation/operations_test.go +++ /dev/null @@ -1 +0,0 @@ -package simulation diff --git a/x/swap/simulation/params.go b/x/swap/simulation/params.go index 2a3d9844..507e3a71 100644 --- a/x/swap/simulation/params.go +++ b/x/swap/simulation/params.go @@ -1,24 +1,11 @@ package simulation import ( - "fmt" "math/rand" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - sdksimulation "github.com/cosmos/cosmos-sdk/x/simulation" - "github.com/sentinel-official/hub/x/swap/types" + simulationtypes "github.com/cosmos/cosmos-sdk/types/simulation" ) -func ParamChanges(r *rand.Rand) []simtypes.ParamChange { - return []simtypes.ParamChange{ - sdksimulation.NewSimParamChange(types.ModuleName, string(types.KeySwapDenom), func(r *rand.Rand) string { - return fmt.Sprintf("%s", GetRandomSwapDenom(r)) - }), - sdksimulation.NewSimParamChange(types.ModuleName, string(types.KeySwapEnabled), func(r *rand.Rand) string { - return fmt.Sprintf("%v", GetRandomSwapEnabled(r)) - }), - sdksimulation.NewSimParamChange(types.ModuleName, string(types.KeyApproveBy), func(r *rand.Rand) string { - return fmt.Sprintf("%s", GetRandomApproveBy(r)) - }), - } +func ParamChanges(_ *rand.Rand) []simulationtypes.ParamChange { + return nil } diff --git a/x/swap/simulation/params_test.go b/x/swap/simulation/params_test.go deleted file mode 100644 index c8ffd736..00000000 --- a/x/swap/simulation/params_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package simulation - -import ( - "math/rand" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestParamChanges(t *testing.T) { - s := rand.NewSource(1) - r := rand.New(s) - - tests := []struct { - composedKey string - key string - simValue string - subspace string - }{ - {"swap/SwapDenom", "SwapDenom", "HeAerqyNEUzXPFGkqEGqiQWIXnkuHMYZLfGaEFPyynhwJyzAHyfjXUlrGhblTtxWduqtCDMLxiDHIMGFpXzp", "swap"}, - {"swap/SwapEnabled", "SwapEnabled", "true", "swap"}, - {"swap/ApproveBy", "ApproveBy", "sent1xeu4rw4zlakdguwys0c4lwgt4kehckpp0253jd", "swap"}, - } - - paramChanges := ParamChanges(r) - - for i, tt := range paramChanges { - require.Equal(t, tests[i].composedKey, tt.ComposedKey()) - require.Equal(t, tests[i].key, tt.Key()) - require.Equal(t, tests[i].simValue, tt.SimValue()(r)) - require.Equal(t, tests[i].subspace, tt.Subspace()) - } -} diff --git a/x/swap/types/errors.go b/x/swap/types/errors.go index da74aab1..78b1a10e 100644 --- a/x/swap/types/errors.go +++ b/x/swap/types/errors.go @@ -5,14 +5,19 @@ import ( ) var ( - ErrorMarshal = errors.Register(ModuleName, 101, "error occurred while marshalling") - ErrorUnmarshal = errors.Register(ModuleName, 102, "error occurred while unmarshalling") - ErrorUnknownMsgType = errors.Register(ModuleName, 103, "unknown message type") - ErrorUnknownQueryType = errors.Register(ModuleName, 104, "unknown query type") - ErrorInvalidFieldFrom = errors.Register(ModuleName, 105, "invalid value for field from; expected a valid address") - ErrorSwapIsDisabled = errors.Register(ModuleName, 106, "swap is disabled") - ErrorUnauthorized = errors.Register(ModuleName, 107, "unauthorized") - ErrorDuplicateSwap = errors.Register(ModuleName, 108, "duplicate swap") - ErrorInvalidFieldReceiver = errors.Register(ModuleName, 109, "invalid value for field receiver; expected a valid address") - ErrorInvalidFieldAmount = errors.Register(ModuleName, 110, "invalid value for field amount; expected a value greater than or equal to 100") + ErrorInvalidField = errors.Register(ModuleName, 101, "invalid field") + ErrorInvalidFrom = errors.Register(ModuleName, 102, "invalid from") + ErrorInvalidReceiver = errors.Register(ModuleName, 103, "invalid receiver") + ErrorInvalidTxHash = errors.Register(ModuleName, 104, "invalid tx_hash") + ErrorInvalidAmount = errors.Register(ModuleName, 105, "invalid amount") +) + +var ( + ErrorSwapIsDisabled = errors.Register(ModuleName, 201, "swap is disabled") + ErrorUnauthorized = errors.Register(ModuleName, 202, "unauthorized") + ErrorDuplicateSwap = errors.Register(ModuleName, 203, "duplicate swap") +) + +var ( + ErrorUnknownMsgType = errors.Register(ModuleName, 301, "unknown message type") ) diff --git a/x/swap/types/genesis.go b/x/swap/types/genesis.go index 2ae8f97a..19108042 100644 --- a/x/swap/types/genesis.go +++ b/x/swap/types/genesis.go @@ -11,30 +11,30 @@ func NewGenesisState(swaps Swaps, params Params) *GenesisState { } } -func (s *GenesisState) Validate() error { - if err := s.Params.Validate(); err != nil { - return err - } +func DefaultGenesisState() *GenesisState { + return NewGenesisState(nil, DefaultParams()) +} - for _, item := range s.Swaps { - if err := item.Validate(); err != nil { - return err - } +func (m *GenesisState) Validate() error { + if err := m.Params.Validate(); err != nil { + return err } swaps := make(map[string]bool) - for _, item := range s.Swaps { + for _, item := range m.Swaps { txHash := item.GetTxHash().String() if swaps[txHash] { - return fmt.Errorf("duplicate swap for tx_hash %s", txHash) + return fmt.Errorf("found duplicate swap for tx_hash %s", txHash) } swaps[txHash] = true } - return nil -} + for _, item := range m.Swaps { + if err := item.Validate(); err != nil { + return err + } + } -func DefaultGenesisState() *GenesisState { - return NewGenesisState(nil, DefaultParams()) + return nil } diff --git a/x/swap/types/keys.go b/x/swap/types/keys.go index 2671d125..71184b30 100644 --- a/x/swap/types/keys.go +++ b/x/swap/types/keys.go @@ -17,13 +17,17 @@ var ( ) var ( - PrecisionLoss = sdk.NewInt(100) + TypeMsgSwapRequest = ModuleName + ":swap" ) var ( EventModuleName = EventModule{Name: ModuleName} ) +var ( + PrecisionLoss = sdk.NewInt(100) +) + var ( SwapKeyPrefix = []byte{0x10} ) diff --git a/x/swap/types/msg.go b/x/swap/types/msg.go index 64e35850..a7a65d52 100644 --- a/x/swap/types/msg.go +++ b/x/swap/types/msg.go @@ -1,9 +1,12 @@ package types import ( - "fmt" - sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/errors" +) + +var ( + _ sdk.Msg = (*MsgSwapRequest)(nil) ) func NewMsgSwapRequest(from sdk.AccAddress, txHash EthereumHash, receiver sdk.AccAddress, amount sdk.Int) *MsgSwapRequest { @@ -20,20 +23,42 @@ func (m *MsgSwapRequest) Route() string { } func (m *MsgSwapRequest) Type() string { - return fmt.Sprintf("%s:swap", ModuleName) + return TypeMsgSwapRequest } func (m *MsgSwapRequest) ValidateBasic() error { + if m.From == "" { + return errors.Wrap(ErrorInvalidFrom, "from cannot be empty") + } if _, err := sdk.AccAddressFromBech32(m.From); err != nil { - return ErrorInvalidFieldFrom + return errors.Wrapf(ErrorInvalidFrom, "%s", err) + } + if m.Receiver == "" { + return errors.Wrap(ErrorInvalidReceiver, "receiver cannot be empty") } - if _, err := sdk.AccAddressFromBech32(m.Receiver); err != nil { - return ErrorInvalidFieldReceiver + return errors.Wrapf(ErrorInvalidReceiver, "%s", err) + } + if m.TxHash == nil { + return errors.Wrap(ErrorInvalidTxHash, "tx_hash cannot be nil") + } + if len(m.TxHash) == 0 { + return errors.Wrap(ErrorInvalidTxHash, "tx_hash cannot be empty") + } + if len(m.TxHash) < EthereumHashLength { + return errors.Wrapf(ErrorInvalidTxHash, "tx_hash length cannot be less than %d", EthereumHashLength) + } + if len(m.TxHash) > EthereumHashLength { + return errors.Wrapf(ErrorInvalidTxHash, "tx_hash length cannot be greater than %d", EthereumHashLength) + } + if m.Amount.IsNegative() { + return errors.Wrap(ErrorInvalidAmount, "amount cannot be negative") + } + if m.Amount.IsZero() { + return errors.Wrap(ErrorInvalidAmount, "amount cannot be zero") } - if m.Amount.LT(PrecisionLoss) { - return ErrorInvalidFieldAmount + return errors.Wrapf(ErrorInvalidAmount, "amount cannot be less than %s", PrecisionLoss) } return nil diff --git a/x/swap/types/msg_test.go b/x/swap/types/msg_test.go deleted file mode 100644 index 6da4746a..00000000 --- a/x/swap/types/msg_test.go +++ /dev/null @@ -1,37 +0,0 @@ -package types - -import ( - "errors" - "testing" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -func TestMsgSwapRequest_ValidateBasic(t *testing.T) { - - correctAddress, err := sdk.AccAddressFromBech32("cosmos1hlzx7raug6wea9fs955nkzwe2xaa65gnpmddal") - if err != nil { - t.Errorf("invalid address provided: %s", err) - } - - tests := []struct { - name string - m *MsgSwapRequest - want error - }{ - {"nil from address", NewMsgSwapRequest(nil, EthereumHash{}, nil, sdk.NewInt(0)), ErrorInvalidFieldFrom}, - {"empty from address", NewMsgSwapRequest(sdk.AccAddress{}, EthereumHash{}, nil, sdk.NewInt(0)), ErrorInvalidFieldFrom}, - {"nil receiver address", NewMsgSwapRequest(correctAddress, EthereumHash{}, nil, sdk.NewInt(0)), ErrorInvalidFieldReceiver}, - {"empty receiver address", NewMsgSwapRequest(correctAddress, EthereumHash{}, sdk.AccAddress{}, sdk.NewInt(0)), ErrorInvalidFieldReceiver}, - {"invalid amount", NewMsgSwapRequest(correctAddress, EthereumHash{}, correctAddress, sdk.NewInt(10)), ErrorInvalidFieldAmount}, - {"valid", NewMsgSwapRequest(correctAddress, EthereumHash{}, correctAddress, sdk.NewInt(1000)), nil}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := tt.m.ValidateBasic(); !errors.Is(err, tt.want) { - t.Errorf("ValidateBasic() = %v, want %v", err, tt.want) - } - }) - } -} diff --git a/x/swap/types/params.go b/x/swap/types/params.go index ee16a858..5ad54a50 100644 --- a/x/swap/types/params.go +++ b/x/swap/types/params.go @@ -4,6 +4,7 @@ import ( "fmt" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/errors" params "github.com/cosmos/cosmos-sdk/x/params/types" ) @@ -23,27 +24,28 @@ var ( _ params.ParamSet = (*Params)(nil) ) -func (p *Params) Validate() error { - if err := sdk.ValidateDenom(p.SwapDenom); err != nil { - return err +func (m *Params) Validate() error { + if m.SwapDenom == "" { + return fmt.Errorf("swap_denom cannot be emtpy") } - - approveBy, err := sdk.AccAddressFromBech32(p.ApproveBy) - if err != nil { - return err + if err := sdk.ValidateDenom(m.SwapDenom); err != nil { + return errors.Wrapf(err, "invalid swap_denom %s", m.SwapDenom) + } + if m.ApproveBy == "" { + return fmt.Errorf("approve_by cannot be empty") } - if approveBy == nil || approveBy.Empty() { - return fmt.Errorf("approve_by should not nil or empty") + if _, err := sdk.AccAddressFromBech32(m.ApproveBy); err != nil { + return errors.Wrapf(err, "invalid approve_by %s", m.ApproveBy) } return nil } -func (p *Params) ParamSetPairs() params.ParamSetPairs { +func (m *Params) ParamSetPairs() params.ParamSetPairs { return params.ParamSetPairs{ { Key: KeySwapEnabled, - Value: &p.SwapEnabled, + Value: &m.SwapEnabled, ValidatorFn: func(v interface{}) error { _, ok := v.(bool) if !ok { @@ -55,28 +57,37 @@ func (p *Params) ParamSetPairs() params.ParamSetPairs { }, { Key: KeySwapDenom, - Value: &p.SwapDenom, + Value: &m.SwapDenom, ValidatorFn: func(v interface{}) error { value, ok := v.(string) if !ok { return fmt.Errorf("invalid parameter type %T", v) } - return sdk.ValidateDenom(value) + if value == "" { + return fmt.Errorf("value cannot be emtpy") + } + if err := sdk.ValidateDenom(value); err != nil { + return errors.Wrapf(err, "invalid value %s", value) + } + + return nil }, }, { Key: KeyApproveBy, - Value: &p.ApproveBy, + Value: &m.ApproveBy, ValidatorFn: func(v interface{}) error { value, ok := v.(string) if !ok { return fmt.Errorf("invalid parameter type %T", v) } - _, err := sdk.AccAddressFromBech32(value) - if err != nil { - return err + if value == "" { + return fmt.Errorf("value cannot be empty") + } + if _, err := sdk.AccAddressFromBech32(value); err != nil { + return errors.Wrapf(err, "invalid value %s", value) } return nil diff --git a/x/swap/types/swap.go b/x/swap/types/swap.go index 07c5832e..1a129547 100644 --- a/x/swap/types/swap.go +++ b/x/swap/types/swap.go @@ -4,26 +4,48 @@ import ( "fmt" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/errors" ) -func (s *Swap) GetTxHash() (hash EthereumHash) { - return BytesToHash(s.TxHash) +func (m *Swap) GetTxHash() (hash EthereumHash) { + return BytesToHash(m.TxHash) } -func (s *Swap) Validate() error { - if len(s.TxHash) != EthereumHashLength { - return fmt.Errorf("tx_hash length should be 32") +func (m *Swap) Validate() error { + if m.TxHash == nil { + return fmt.Errorf("tx_hash cannot be nil") } - - if _, err := sdk.AccAddressFromBech32(s.Receiver); err != nil { - return err + if len(m.TxHash) == 0 { + return fmt.Errorf("tx_hash cannot be empty") } - - if !s.Amount.IsValid() { - return fmt.Errorf("amount should be valid") + if len(m.TxHash) < EthereumHashLength { + return fmt.Errorf("tx_hash length cannot be less than %d", EthereumHashLength) + } + if len(m.TxHash) > EthereumHashLength { + return fmt.Errorf("tx_hash length cannot be greater than %d", EthereumHashLength) + } + if m.Receiver == "" { + return fmt.Errorf("receiver cannot be empty") + } + if _, err := sdk.AccAddressFromBech32(m.Receiver); err != nil { + return errors.Wrapf(err, "invalid receiver %s", m.Receiver) + } + if m.Amount.IsNegative() { + return fmt.Errorf("amount cannot be negative") + } + if m.Amount.IsZero() { + return fmt.Errorf("amount cannot be zero") + } + if m.Amount.Amount.LT(PrecisionLoss) { + return fmt.Errorf("amount cannot be less than %s", PrecisionLoss) + } + if !m.Amount.IsValid() { + return fmt.Errorf("amount must be valid") } return nil } -type Swaps []Swap +type ( + Swaps []Swap +) diff --git a/x/vpn/expected/keeper.go b/x/vpn/expected/keeper.go index 4ff36b28..fe03a62b 100644 --- a/x/vpn/expected/keeper.go +++ b/x/vpn/expected/keeper.go @@ -8,3 +8,8 @@ import ( type AccountKeeper interface { GetAccount(ctx sdk.Context, address sdk.AccAddress) authtypes.AccountI } + +type BankKeeper interface { + SendCoins(ctx sdk.Context, from sdk.AccAddress, to sdk.AccAddress, coins sdk.Coins) error + SpendableCoins(ctx sdk.Context, address sdk.AccAddress) sdk.Coins +} diff --git a/x/vpn/keeper/keeper.go b/x/vpn/keeper/keeper.go index 739ecf05..6741f46c 100644 --- a/x/vpn/keeper/keeper.go +++ b/x/vpn/keeper/keeper.go @@ -29,8 +29,14 @@ type Keeper struct { Session sessionkeeper.Keeper } -func NewKeeper(cdc codec.BinaryMarshaler, key sdk.StoreKey, paramsKeeper paramskeeper.Keeper, accountKeeper authkeeper.AccountKeeper, - bankKeeper bankkeeper.Keeper, distributionKeeper distributionkeeper.Keeper) Keeper { +func NewKeeper( + cdc codec.BinaryMarshaler, + key sdk.StoreKey, + paramsKeeper paramskeeper.Keeper, + accountKeeper authkeeper.AccountKeeper, + bankKeeper bankkeeper.Keeper, + distributionKeeper distributionkeeper.Keeper, +) Keeper { var ( depositKeeper = depositkeeper.NewKeeper(cdc, key) providerKeeper = providerkeeper.NewKeeper(cdc, key, paramsKeeper.Subspace(providertypes.ParamsSubspace)) diff --git a/x/vpn/module.go b/x/vpn/module.go index 8692a6c2..cd8b6df1 100644 --- a/x/vpn/module.go +++ b/x/vpn/module.go @@ -10,7 +10,7 @@ import ( codectypes "github.com/cosmos/cosmos-sdk/codec/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" - "github.com/cosmos/cosmos-sdk/types/simulation" + sdksimulation "github.com/cosmos/cosmos-sdk/types/simulation" "github.com/gorilla/mux" "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/spf13/cobra" @@ -31,6 +31,7 @@ import ( "github.com/sentinel-official/hub/x/vpn/client/cli" "github.com/sentinel-official/hub/x/vpn/expected" "github.com/sentinel-official/hub/x/vpn/keeper" + "github.com/sentinel-official/hub/x/vpn/simulation" "github.com/sentinel-official/hub/x/vpn/types" ) @@ -88,14 +89,18 @@ func (a AppModuleBasic) GetQueryCmd() *cobra.Command { type AppModule struct { AppModuleBasic - ak expected.AccountKeeper - k keeper.Keeper + cdc codec.Marshaler + ak expected.AccountKeeper + bk expected.BankKeeper + k keeper.Keeper } -func NewAppModule(ak expected.AccountKeeper, k keeper.Keeper) AppModule { +func NewAppModule(cdc codec.Marshaler, ak expected.AccountKeeper, bk expected.BankKeeper, k keeper.Keeper) AppModule { return AppModule{ - ak: ak, - k: k, + cdc: cdc, + ak: ak, + bk: bk, + k: k, } } @@ -144,18 +149,20 @@ func (a AppModule) EndBlock(ctx sdk.Context, _ abcitypes.RequestEndBlock) []abci return EndBlock(ctx, a.k) } -func (a AppModule) GenerateGenesisState(_ *module.SimulationState) {} +func (a AppModule) GenerateGenesisState(state *module.SimulationState) { + simulation.RandomizedGenesisState(state) +} -func (a AppModule) ProposalContents(_ module.SimulationState) []simulation.WeightedProposalContent { +func (a AppModule) ProposalContents(_ module.SimulationState) []sdksimulation.WeightedProposalContent { return nil } -func (a AppModule) RandomizedParams(_ *rand.Rand) []simulation.ParamChange { - return nil +func (a AppModule) RandomizedParams(r *rand.Rand) []sdksimulation.ParamChange { + return simulation.RandomizedParams(r) } func (a AppModule) RegisterStoreDecoder(_ sdk.StoreDecoderRegistry) {} -func (a AppModule) WeightedOperations(_ module.SimulationState) []simulation.WeightedOperation { - return nil +func (a AppModule) WeightedOperations(state module.SimulationState) []sdksimulation.WeightedOperation { + return simulation.WeightedOperations(state.AppParams, a.cdc, a.ak, a.bk, a.k) } diff --git a/x/vpn/simulation/genesis.go b/x/vpn/simulation/genesis.go new file mode 100644 index 00000000..fa3ce97e --- /dev/null +++ b/x/vpn/simulation/genesis.go @@ -0,0 +1,64 @@ +package simulation + +import ( + "github.com/cosmos/cosmos-sdk/types/module" + + depositsimulation "github.com/sentinel-official/hub/x/deposit/simulation" + nodesimulation "github.com/sentinel-official/hub/x/node/simulation" + plansimulation "github.com/sentinel-official/hub/x/plan/simulation" + providersimulation "github.com/sentinel-official/hub/x/provider/simulation" + sessionsimulation "github.com/sentinel-official/hub/x/session/simulation" + subscriptionsimulation "github.com/sentinel-official/hub/x/subscription/simulation" + "github.com/sentinel-official/hub/x/vpn/types" +) + +func RandomizedGenesisState(state *module.SimulationState) { + var ( + depositGenesisState = depositsimulation.RandomizedGenesisState(state) + providerGenesisState = providersimulation.RandomizedGenesisState(state) + nodeGenesisState = nodesimulation.RandomizedGenesisState(state) + planGenesisState = plansimulation.RandomizedGenesisState(state) + subscriptionGenesisState = subscriptionsimulation.RandomizedGenesisState(state) + sessionGenesisState = sessionsimulation.RandomizedGenesisState(state) + ) + + for i := 0; i < len(nodeGenesisState.Nodes); i++ { + if state.Rand.Intn(2) == 0 { + nodeGenesisState.Nodes[i].Provider = providersimulation.RandomProvider( + state.Rand, + providerGenesisState.Providers, + ).Address + } + } + + for i := 0; i < len(planGenesisState); i++ { + if state.Rand.Intn(2) == 0 { + planGenesisState[i].Plan.Provider = providersimulation.RandomProvider( + state.Rand, + providerGenesisState.Providers, + ).Address + } + + for j := 0; j < len(nodeGenesisState.Nodes); j++ { + if nodeGenesisState.Nodes[j].Provider == planGenesisState[i].Plan.Provider { + if state.Rand.Intn(2) == 0 { + planGenesisState[i].Nodes = append( + planGenesisState[i].Nodes, + nodeGenesisState.Nodes[j].Address, + ) + } + } + } + } + + state.GenState[types.ModuleName] = state.Cdc.MustMarshalJSON( + types.NewGenesisState( + depositGenesisState, + providerGenesisState, + nodeGenesisState, + planGenesisState, + subscriptionGenesisState, + sessionGenesisState, + ), + ) +} diff --git a/x/vpn/simulation/operations.go b/x/vpn/simulation/operations.go new file mode 100644 index 00000000..2e2e8a37 --- /dev/null +++ b/x/vpn/simulation/operations.go @@ -0,0 +1,31 @@ +package simulation + +import ( + "github.com/cosmos/cosmos-sdk/codec" + simulationtypes "github.com/cosmos/cosmos-sdk/types/simulation" + + nodesimulation "github.com/sentinel-official/hub/x/node/simulation" + plansimulation "github.com/sentinel-official/hub/x/plan/simulation" + providersimulation "github.com/sentinel-official/hub/x/provider/simulation" + sessionsimulation "github.com/sentinel-official/hub/x/session/simulation" + subscriptionsimulation "github.com/sentinel-official/hub/x/subscription/simulation" + "github.com/sentinel-official/hub/x/vpn/expected" + "github.com/sentinel-official/hub/x/vpn/keeper" +) + +func WeightedOperations( + params simulationtypes.AppParams, + cdc codec.JSONMarshaler, + ak expected.AccountKeeper, + bk expected.BankKeeper, + k keeper.Keeper, +) []simulationtypes.WeightedOperation { + var operations []simulationtypes.WeightedOperation + operations = append(operations, providersimulation.WeightedOperations(params, cdc, ak, bk, k.Provider)...) + operations = append(operations, nodesimulation.WeightedOperations(params, cdc, ak, bk, k.Node)...) + operations = append(operations, plansimulation.WeightedOperations(params, cdc, ak, bk, k.Plan)...) + operations = append(operations, subscriptionsimulation.WeightedOperations(params, cdc, ak, bk, k.Subscription)...) + operations = append(operations, sessionsimulation.WeightedOperations(params, cdc, ak, bk, k.Session)...) + + return operations +} diff --git a/x/vpn/simulation/params.go b/x/vpn/simulation/params.go new file mode 100644 index 00000000..15e97f14 --- /dev/null +++ b/x/vpn/simulation/params.go @@ -0,0 +1,22 @@ +package simulation + +import ( + "math/rand" + + simulationtypes "github.com/cosmos/cosmos-sdk/types/simulation" + + nodesimulation "github.com/sentinel-official/hub/x/node/simulation" + providersimulation "github.com/sentinel-official/hub/x/provider/simulation" + sessionsimulation "github.com/sentinel-official/hub/x/session/simulation" + subscriptionsimulation "github.com/sentinel-official/hub/x/subscription/simulation" +) + +func RandomizedParams(r *rand.Rand) []simulationtypes.ParamChange { + var params []simulationtypes.ParamChange + params = append(params, providersimulation.ParamChanges(r)...) + params = append(params, nodesimulation.ParamChanges(r)...) + params = append(params, subscriptionsimulation.ParamChanges(r)...) + params = append(params, sessionsimulation.ParamChanges(r)...) + + return params +} diff --git a/x/vpn/types/errors.go b/x/vpn/types/errors.go index 325ee540..1b712ef6 100644 --- a/x/vpn/types/errors.go +++ b/x/vpn/types/errors.go @@ -5,9 +5,5 @@ import ( ) var ( - ErrorMarshal = errors.Register(ModuleName, 101, "error occurred while marshalling") - ErrorUnmarshal = errors.Register(ModuleName, 102, "error occurred while unmarshalling") - ErrorUnknownMsgType = errors.Register(ModuleName, 103, "unknown message type") - ErrorUnknownQueryType = errors.Register(ModuleName, 104, "unknown query type") - ErrorInvalidField = errors.Register(ModuleName, 105, "invalid field") + ErrorUnknownMsgType = errors.Register(ModuleName, 301, "unknown message type") ) diff --git a/x/vpn/types/genesis.go b/x/vpn/types/genesis.go index 7b5b3b8e..f2b62049 100644 --- a/x/vpn/types/genesis.go +++ b/x/vpn/types/genesis.go @@ -1,6 +1,8 @@ package types import ( + "github.com/cosmos/cosmos-sdk/types/errors" + deposittypes "github.com/sentinel-official/hub/x/deposit/types" nodetypes "github.com/sentinel-official/hub/x/node/types" plantypes "github.com/sentinel-official/hub/x/plan/types" @@ -9,8 +11,14 @@ import ( subscriptiontypes "github.com/sentinel-official/hub/x/subscription/types" ) -func NewGenesisState(deposits deposittypes.GenesisState, providers *providertypes.GenesisState, nodes *nodetypes.GenesisState, - plans plantypes.GenesisState, subscriptions *subscriptiontypes.GenesisState, sessions *sessiontypes.GenesisState) *GenesisState { +func NewGenesisState( + deposits deposittypes.GenesisState, + providers *providertypes.GenesisState, + nodes *nodetypes.GenesisState, + plans plantypes.GenesisState, + subscriptions *subscriptiontypes.GenesisState, + sessions *sessiontypes.GenesisState, +) *GenesisState { return &GenesisState{ Deposits: deposits, Providers: providers, @@ -21,29 +29,6 @@ func NewGenesisState(deposits deposittypes.GenesisState, providers *providertype } } -func (s *GenesisState) Validate() error { - if err := deposittypes.ValidateGenesis(s.Deposits); err != nil { - return err - } - if err := providertypes.ValidateGenesis(s.Providers); err != nil { - return err - } - if err := nodetypes.ValidateGenesis(s.Nodes); err != nil { - return err - } - if err := plantypes.ValidateGenesis(s.Plans); err != nil { - return err - } - if err := subscriptiontypes.ValidateGenesis(s.Subscriptions); err != nil { - return err - } - if err := sessiontypes.ValidateGenesis(s.Sessions); err != nil { - return err - } - - return nil -} - func DefaultGenesisState() *GenesisState { return NewGenesisState( deposittypes.DefaultGenesisState(), @@ -54,3 +39,26 @@ func DefaultGenesisState() *GenesisState { sessiontypes.DefaultGenesisState(), ) } + +func (m *GenesisState) Validate() error { + if err := deposittypes.ValidateGenesis(m.Deposits); err != nil { + return errors.Wrapf(err, "invalid deposit genesis") + } + if err := providertypes.ValidateGenesis(m.Providers); err != nil { + return errors.Wrapf(err, "invalid provider genesis") + } + if err := nodetypes.ValidateGenesis(m.Nodes); err != nil { + return errors.Wrapf(err, "invalid node genesis") + } + if err := plantypes.ValidateGenesis(m.Plans); err != nil { + return errors.Wrapf(err, "invalid plan genesis") + } + if err := subscriptiontypes.ValidateGenesis(m.Subscriptions); err != nil { + return errors.Wrapf(err, "invalid subscription genesis") + } + if err := sessiontypes.ValidateGenesis(m.Sessions); err != nil { + return errors.Wrapf(err, "invalid session genesis") + } + + return nil +} diff --git a/x/vpn/types/init.go b/x/vpn/types/init.go index 4c5239d0..786c8bd2 100644 --- a/x/vpn/types/init.go +++ b/x/vpn/types/init.go @@ -12,6 +12,7 @@ import ( ) func init() { + providertypes.ParamsSubspace = fmt.Sprintf("%s/%s", ModuleName, providertypes.ModuleName) nodetypes.ParamsSubspace = fmt.Sprintf("%s/%s", ModuleName, nodetypes.ModuleName) subscriptiontypes.ParamsSubspace = fmt.Sprintf("%s/%s", ModuleName, subscriptiontypes.ModuleName) sessiontypes.ParamsSubspace = fmt.Sprintf("%s/%s", ModuleName, sessiontypes.ModuleName)