diff --git a/pkg/code/server/grpc/messaging/message_handler.go b/pkg/code/server/grpc/messaging/message_handler.go index 163f5ae7..47d28eb0 100644 --- a/pkg/code/server/grpc/messaging/message_handler.go +++ b/pkg/code/server/grpc/messaging/message_handler.go @@ -3,6 +3,7 @@ package messaging import ( "bytes" "context" + "math" "time" "github.com/mr-tron/base58" @@ -22,6 +23,7 @@ import ( "github.com/code-payments/code-server/pkg/code/limit" "github.com/code-payments/code-server/pkg/code/thirdparty" currency_lib "github.com/code-payments/code-server/pkg/currency" + "github.com/code-payments/code-server/pkg/kin" ) // MessageHandler provides message-specific in addition to the generic message @@ -138,14 +140,21 @@ func (h *RequestToReceiveBillMessageHandler) Validate(ctx context.Context, rende quarks = &typed.Exact.Quarks if currency != currency_lib.KIN { - return newMessageValidationError("exact exchange data only supports kin currency") + return newMessageValidationError("exact exchange data is reserved for kin only") + } + + if nativeAmount != math.Trunc(nativeAmount) { + return newMessageValidationError("native amount can't include fractional kin") + } + if *quarks%kin.QuarksPerKin != 0 { + return newMessageValidationError("quark amount can't include fractional kin") } case *messagingpb.RequestToReceiveBill_Partial: currency = currency_lib.Code(typed.Partial.Currency) nativeAmount = typed.Partial.NativeAmount if currency == currency_lib.KIN { - return newMessageValidationError("partial exchange data only supports fiat currencies") + return newMessageValidationError("partial exchange data is reserved for fiat currencies") } default: return newMessageValidationError("exchange data is nil") diff --git a/pkg/code/server/grpc/messaging/server_test.go b/pkg/code/server/grpc/messaging/server_test.go index f977c1a4..8d3abee2 100644 --- a/pkg/code/server/grpc/messaging/server_test.go +++ b/pkg/code/server/grpc/messaging/server_test.go @@ -420,10 +420,24 @@ func TestSendMessage_RequestToReceiveBill_KinValue_Validation(t *testing.T) { env.server1.assertNoMessages(t, rendezvousKey) env.server1.assertPaymentRequestRecordNotSaved(t, rendezvousKey) + env.client1.resetConf() + env.client1.conf.simulateFractionalNativeAmount = true + sendMessageCall = env.client1.sendRequestToReceiveKinBillMessage(t, rendezvousKey, false, false, true) + sendMessageCall.assertInvalidMessageError(t, "native amount can't include fractional kin") + env.server1.assertNoMessages(t, rendezvousKey) + env.server1.assertPaymentRequestRecordNotSaved(t, rendezvousKey) + + env.client1.resetConf() + env.client1.conf.simulateFractionalQuarkAmount = true + sendMessageCall = env.client1.sendRequestToReceiveKinBillMessage(t, rendezvousKey, false, false, true) + sendMessageCall.assertInvalidMessageError(t, "quark amount can't include fractional kin") + env.server1.assertNoMessages(t, rendezvousKey) + env.server1.assertPaymentRequestRecordNotSaved(t, rendezvousKey) + env.client1.resetConf() env.client1.conf.simulateInvalidCurrency = true sendMessageCall = env.client1.sendRequestToReceiveKinBillMessage(t, rendezvousKey, false, false, true) - sendMessageCall.assertInvalidMessageError(t, "exact exchange data only supports kin currency") + sendMessageCall.assertInvalidMessageError(t, "exact exchange data is reserved for kin only") env.server1.assertNoMessages(t, rendezvousKey) env.server1.assertPaymentRequestRecordNotSaved(t, rendezvousKey) @@ -519,7 +533,7 @@ func TestSendMessage_RequestToReceiveBill_FiatValue_Validation(t *testing.T) { env.client1.resetConf() env.client1.conf.simulateInvalidCurrency = true sendMessageCall = env.client1.sendRequestToReceiveFiatBillMessage(t, rendezvousKey, false, false, true) - sendMessageCall.assertInvalidMessageError(t, "partial exchange data only supports fiat currencies") + sendMessageCall.assertInvalidMessageError(t, "partial exchange data is reserved for fiat currencies") env.server1.assertNoMessages(t, rendezvousKey) env.server1.assertPaymentRequestRecordNotSaved(t, rendezvousKey) diff --git a/pkg/code/server/grpc/messaging/testutil.go b/pkg/code/server/grpc/messaging/testutil.go index 4b87eb54..19e2011e 100644 --- a/pkg/code/server/grpc/messaging/testutil.go +++ b/pkg/code/server/grpc/messaging/testutil.go @@ -234,11 +234,13 @@ type clientConf struct { // Simulations for invalid exchange data - simulateInvalidCurrency bool - simulateInvalidExchangeRate bool - simulateInvalidNativeAmount bool - simulateSmallNativeAmount bool - simulateLargeNativeAmount bool + simulateInvalidCurrency bool + simulateInvalidExchangeRate bool + simulateInvalidNativeAmount bool + simulateSmallNativeAmount bool + simulateLargeNativeAmount bool + simulateFractionalNativeAmount bool + simulateFractionalQuarkAmount bool // Simulations for invalid relationships @@ -588,16 +590,16 @@ func (c *clientEnv) sendRequestToReceiveKinBillMessage(t *testing.T, rendezvousK exchangeData := &transactionpb.ExchangeData{ Currency: "kin", ExchangeRate: 1.0, - NativeAmount: 10_000, - Quarks: kin.ToQuarks(10_000), + NativeAmount: 10_001, + Quarks: kin.ToQuarks(10_001), } if c.conf.simulateInvalidCurrency { exchangeData.Currency = "usd" } if c.conf.simulateInvalidExchangeRate { - exchangeData.ExchangeRate *= 1.5 - exchangeData.NativeAmount *= 1.5 + exchangeData.ExchangeRate *= 2.0 + exchangeData.NativeAmount *= 2.0 } if c.conf.simulateInvalidNativeAmount { exchangeData.NativeAmount += 2 @@ -607,8 +609,14 @@ func (c *clientEnv) sendRequestToReceiveKinBillMessage(t *testing.T, rendezvousK exchangeData.Quarks = kin.ToQuarks(1) } if c.conf.simulateLargeNativeAmount { - exchangeData.NativeAmount = 100001 - exchangeData.Quarks = kin.ToQuarks(100001) + exchangeData.NativeAmount = 100_001 + exchangeData.Quarks = kin.ToQuarks(100_001) + } + if c.conf.simulateFractionalNativeAmount { + exchangeData.NativeAmount += 0.1 + } + if c.conf.simulateFractionalQuarkAmount { + exchangeData.Quarks += kin.QuarksPerKin / 10 } msg := &messagingpb.RequestToReceiveBill{ diff --git a/pkg/code/server/web/paymentrequest/model.go b/pkg/code/server/web/paymentrequest/model.go index 5daf584d..ba5bf5b6 100644 --- a/pkg/code/server/web/paymentrequest/model.go +++ b/pkg/code/server/web/paymentrequest/model.go @@ -226,6 +226,7 @@ func newTrustlessPaymentRequestFromHttpContext(r *http.Request) (*trustlessPayme return nil, errors.New("intent is not a public key") } + // Validation occurs at the messaging service var protoMesage messagingpb.RequestToReceiveBill messageBytes, err := base64.RawURLEncoding.DecodeString(httpRequestBody.Message) if err != nil { @@ -245,52 +246,6 @@ func newTrustlessPaymentRequestFromHttpContext(r *http.Request) (*trustlessPayme } copy(signature[:], decodedSignature) - _, err = common.NewAccountFromProto(protoMesage.RequestorAccount) - if err != nil { - return nil, errors.New("destination is not a public key") - } - - var currency currency_lib.Code - var amount float64 - switch typed := protoMesage.ExchangeData.(type) { - case *messagingpb.RequestToReceiveBill_Exact: - currency = currency_lib.Code(strings.ToLower(typed.Exact.Currency)) - amount = float64(kin.FromQuarks(typed.Exact.Quarks)) // Because of minimum bucket sizes - - if currency != currency_lib.KIN { - return nil, errors.New("exact exchange data is reserved for kin only") - } - - if typed.Exact.ExchangeRate != 1.0 { - return nil, errors.New("kin exchange rate must be 1.0") - } else if kin.ToQuarks(uint64(amount)) != typed.Exact.Quarks { - return nil, errors.New("kin amount cannot be fractional") - } else if amount != typed.Exact.NativeAmount { - return nil, errors.New("kin amount doesn't match quarks") - } - case *messagingpb.RequestToReceiveBill_Partial: - currency = currency_lib.Code(strings.ToLower(typed.Partial.Currency)) - amount = typed.Partial.NativeAmount - - if currency == currency_lib.KIN { - return nil, errors.New("partial exchange data is reserved for fiat currencies") - } - default: - return nil, errors.New("exchange data not provided") - } - - limits, ok := limit.MicroPaymentLimits[currency] - if !ok { - return nil, errors.Errorf("%s currency is not currently supported", currency) - } else if amount > limits.Max { - return nil, errors.Errorf("%s currency has a maximum amount of %.2f", currency, limits.Max) - } else if amount < limits.Min { - return nil, errors.Errorf("%s currency has a minimum amount of %.2f", currency, limits.Min) - } - - // todo: Validate domain fields with user-friendly error messaging. The - // messaging service will do this for now, and will be translated. - if httpRequestBody.Webhook != nil { err = netutil.ValidateHttpUrl(*httpRequestBody.Webhook, true, false) if err != nil {