From 6aa7aacb9b0eb048a8f855192e9ed7d3405e8f6a Mon Sep 17 00:00:00 2001 From: JCrawsh Date: Thu, 4 Jan 2024 14:24:04 +0000 Subject: [PATCH] chore: passing tests for all encode methods --- binary-codec/main.go | 133 ++++----- binary-codec/main_test.go | 382 +++++++++++++------------- binary-codec/types/account_id.go | 17 +- binary-codec/types/account_id_test.go | 50 ++++ binary-codec/types/blob.go | 8 +- binary-codec/types/blob_test.go | 43 +++ binary-codec/types/hash.go | 5 + binary-codec/types/hash128.go | 2 + binary-codec/types/hash128_test.go | 50 ++++ binary-codec/types/hash160.go | 3 + binary-codec/types/hash160_test.go | 44 +++ binary-codec/types/hash256.go | 2 + binary-codec/types/hash256_test.go | 52 ++++ binary-codec/types/st_object.go | 32 ++- 14 files changed, 553 insertions(+), 270 deletions(-) create mode 100644 binary-codec/types/account_id_test.go create mode 100644 binary-codec/types/blob_test.go create mode 100644 binary-codec/types/hash128_test.go create mode 100644 binary-codec/types/hash160_test.go create mode 100644 binary-codec/types/hash256_test.go diff --git a/binary-codec/main.go b/binary-codec/main.go index f2d88b46..a3a2e59f 100644 --- a/binary-codec/main.go +++ b/binary-codec/main.go @@ -4,9 +4,9 @@ import ( "bytes" "encoding/hex" "errors" + "reflect" "strings" - "github.com/xyield/xrpl-go/binary-codec/definitions" "github.com/xyield/xrpl-go/model/transactions" "github.com/xyield/xrpl-go/binary-codec/serdes" @@ -21,10 +21,11 @@ const ( txSigPrefix = "53545800" ) -// Encode converts a JSON transaction object to a hex string in the canonical binary format. -// The binary format is defined in XRPL's core codebase. -func Encode(tx transactions.Tx) (string, error) { - st := &types.STObject{} +func encode(tx transactions.Tx, onlySigning bool, mutations map[string]types.FieldMutation) (string, error) { + st := &types.STObject{ + OnlySigning: onlySigning, + Mutations: mutations, + } b, err := st.FromJson(tx) if err != nil { return "", err @@ -33,58 +34,69 @@ func Encode(tx transactions.Tx) (string, error) { return strings.ToUpper(hex.EncodeToString(b)), nil } +// Encode converts a JSON transaction object to a hex string in the canonical binary format. +// The binary format is defined in XRPL's core codebase. +func Encode(tx transactions.Tx, onlySigning bool) (string, error) { + return encode(tx, onlySigning, nil) +} + // EncodeForMultiSign: encodes a transaction into binary format in preparation for providing one // signature towards a multi-signed transaction. // (Only encodes fields that are intended to be signed.) -// func EncodeForMultisigning(json map[string]any, xrpAccountID string) (string, error) { - -// st := &types.AccountID{} +func EncodeForMultisigning(tx transactions.Tx, xrpAccountID string) (string, error) { -// // SigningPubKey is required for multi-signing but should be set to empty string. + st := &types.AccountID{} -// json["SigningPubKey"] = "" - -// suffix, err := st.FromJson(xrpAccountID) -// if err != nil { -// return "", err -// } + suffix, err := st.FromJson(xrpAccountID) + if err != nil { + return "", err + } -// encoded, err := Encode(removeNonSigningFields(json)) + // SigningPubKey is required for multi-signing but should be set to empty string. + err = setFieldFromTx(tx, "SigningPubKey", "placeholder", func(v any) bool { + return v.(string) == "" + }) + if err != nil { + return "", err + } + encoded, err := encode(tx, true, map[string]types.FieldMutation{ + "SigningPubKey": types.Zero(), + }) -// if err != nil { -// return "", err -// } + if err != nil { + return "", err + } -// return strings.ToUpper(txMultiSigPrefix + encoded + hex.EncodeToString(suffix)), nil -// } + return strings.ToUpper(txMultiSigPrefix + encoded + hex.EncodeToString(suffix)), nil +} // Encodes a transaction into binary format in preparation for signing. -// func EncodeForSigning(json map[string]any) (string, error) { +func EncodeForSigning(tx transactions.Tx) (string, error) { -// encoded, err := Encode(removeNonSigningFields(json)) + encoded, err := Encode(tx, true) -// if err != nil { -// return "", err -// } + if err != nil { + return "", err + } -// return strings.ToUpper(txSigPrefix + encoded), nil -// } + return strings.ToUpper(txSigPrefix + encoded), nil +} // EncodeForPaymentChannelClaim: encodes a payment channel claim into binary format in preparation for signing. -func EncodeForSigningClaim(json map[string]any) (string, error) { +func EncodeForSigningClaim(tx transactions.PaymentChannelClaim) (string, error) { - if json["Channel"] == nil || json["Amount"] == nil { + if tx.Channel == "" || tx.Amount == 0 { return "", ErrSigningClaimFieldNotFound } - channel, err := types.NewHash256().FromJson(json["Channel"]) + channel, err := types.NewHash256().FromJson(tx.Channel) if err != nil { return "", err } t := &types.Amount{} - amount, err := t.FromJson(json["Amount"]) + amount, err := t.FromJson(tx.Amount) if err != nil { return "", err @@ -98,20 +110,6 @@ func EncodeForSigningClaim(json map[string]any) (string, error) { return strings.ToUpper(paymentChannelClaimPrefix + hex.EncodeToString(channel) + hex.EncodeToString(amount)), nil } -// removeNonSigningFields removes the fields from a JSON transaction object that should not be signed. -func removeNonSigningFields(json map[string]any) map[string]any { - - for k := range json { - fi, _ := definitions.Get().GetFieldInstanceByFieldName(k) - - if fi != nil && !fi.IsSigningField { - delete(json, k) - } - } - - return json -} - // Decode decodes a hex string in the canonical binary format into a JSON transaction object. func Decode(hexEncoded string) (map[string]any, error) { b, err := hex.DecodeString(hexEncoded) @@ -128,29 +126,20 @@ func Decode(hexEncoded string) (map[string]any, error) { return m.(map[string]any), nil } -// func flattenTx(tx transactions.Tx) (map[string]any, error) { -// rv := reflect.ValueOf(tx) -// if rv.Kind() == reflect.Ptr { -// rv = rv.Elem() -// } else { -// return nil, errors.New("invalid transaction") -// } -// m := make(map[string]any) -// baseTx := rv.FieldByName("BaseTx") -// if !baseTx.IsValid() { -// return nil, errors.New("no base tx defined") -// } -// for i := 0; i < baseTx.NumField(); i++ { -// if baseTx.Field(i).IsZero() { -// continue -// } -// m[baseTx.Type().Field(i).Name] = baseTx.Field(i).Interface() -// } -// for i := 0; i < rv.NumField(); i++ { -// if rv.Field(i).IsZero() || rv.Type().Field(i).Name == "BaseTx" { -// continue -// } -// m[rv.Type().Field(i).Name] = rv.Field(i).Interface() -// } -// return m, nil -// } +// Overwrites a field in a transaction with a new value if condition is met. +func setFieldFromTx(tx transactions.Tx, fieldName string, value any, condition func(any) bool) error { + rv := reflect.ValueOf(tx) + if rv.Kind() == reflect.Ptr { + rv = rv.Elem() + } else { + return errors.New("invalid transaction") + } + if !rv.FieldByName(fieldName).IsValid() { + return errors.New("invalid field name") + } + if condition != nil && condition(rv.FieldByName(fieldName).Interface()) { + rv.FieldByName(fieldName).Set(reflect.ValueOf(value)) + return nil + } + return nil +} diff --git a/binary-codec/main_test.go b/binary-codec/main_test.go index 751536f9..4902424f 100644 --- a/binary-codec/main_test.go +++ b/binary-codec/main_test.go @@ -121,7 +121,7 @@ func TestEncode(t *testing.T) { for _, tc := range tt { t.Run(tc.description, func(t *testing.T) { - got, err := Encode(tc.input) + got, err := Encode(tc.input, false) if tc.expectedErr != nil { require.EqualError(t, err, tc.expectedErr.Error()) @@ -241,205 +241,199 @@ func TestDecode(t *testing.T) { } -// func TestEncodeForMultisigning(t *testing.T) { -// tt := []struct { -// description string -// json map[string]any -// accountID string -// output string -// expectedErr error -// }{ -// { -// description: "serialize tx1 for signing correctly", -// json: map[string]any{ -// "Account": "rMBzp8CgpE441cp5PVyA9rpVV7oT8hP3ys", -// "Expiration": 595640108, -// "Fee": "10", -// "Flags": 524288, -// "OfferSequence": 1752791, -// "Sequence": 1752792, -// "SigningPubKey": "03EE83BB432547885C219634A1BC407A9DB0474145D69737D09CCDC63E1DEE7FE3", -// "TakerGets": "15000000000", -// "TakerPays": map[string]any{ -// "currency": "USD", -// "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", -// "value": "7072.8", -// }, -// "TransactionType": "OfferCreate", -// "TxnSignature": "30440220143759437C04F7B61F012563AFE90D8DAFC46E86035E1D965A9CED282C97D4CE02204CFD241E86F17E011298FC1A39B63386C74306A5DE047E213B0F29EFA4571C2C", -// "hash": "73734B611DDA23D3F5F62E20A173B78AB8406AC5015094DA53F53D39B9EDB06C", -// }, -// accountID: "rMBzp8CgpE441cp5PVyA9rpVV7oT8hP3ys", -// output: "534D5400120007220008000024001ABED82A2380BF2C2019001ABED764D55920AC9391400000000000000000000000000055534400000000000A20B3C85F482532A9578DBB3950B85CA06594D165400000037E11D60068400000000000000A73008114DD76483FACDEE26E60D8A586BB58D09F27045C46DD76483FACDEE26E60D8A586BB58D09F27045C46", -// expectedErr: nil, -// }, -// { -// description: "SigningPubKey is not present", -// json: map[string]any{ -// "Account": "rMBzp8CgpE441cp5PVyA9rpVV7oT8hP3ys", -// "Expiration": 595640108, -// "Fee": "10", -// "Flags": 524288, -// "OfferSequence": 1752791, -// "Sequence": 1752792, -// "TakerGets": "15000000000", -// "TakerPays": map[string]any{ -// "currency": "USD", -// "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", -// "value": "7072.8", -// }, -// "TransactionType": "OfferCreate", -// "TxnSignature": "30440220143759437C04F7B61F012563AFE90D8DAFC46E86035E1D965A9CED282C97D4CE02204CFD241E86F17E011298FC1A39B63386C74306A5DE047E213B0F29EFA4571C2C", -// "hash": "73734B611DDA23D3F5F62E20A173B78AB8406AC5015094DA53F53D39B9EDB06C", -// }, -// accountID: "rMBzp8CgpE441cp5PVyA9rpVV7oT8hP3ys", -// output: "534D5400120007220008000024001ABED82A2380BF2C2019001ABED764D55920AC9391400000000000000000000000000055534400000000000A20B3C85F482532A9578DBB3950B85CA06594D165400000037E11D60068400000000000000A73008114DD76483FACDEE26E60D8A586BB58D09F27045C46DD76483FACDEE26E60D8A586BB58D09F27045C46", -// expectedErr: nil, -// }, -// { -// description: "SigningPubKey empty string", -// json: map[string]any{ -// "Account": "rMBzp8CgpE441cp5PVyA9rpVV7oT8hP3ys", -// "Expiration": 595640108, -// "Fee": "10", -// "Flags": 524288, -// "OfferSequence": 1752791, -// "Sequence": 1752792, -// "SigningPubKey": "", -// "TakerGets": "15000000000", -// "TakerPays": map[string]any{ -// "currency": "USD", -// "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", -// "value": "7072.8", -// }, -// "TransactionType": "OfferCreate", -// "TxnSignature": "30440220143759437C04F7B61F012563AFE90D8DAFC46E86035E1D965A9CED282C97D4CE02204CFD241E86F17E011298FC1A39B63386C74306A5DE047E213B0F29EFA4571C2C", -// "hash": "73734B611DDA23D3F5F62E20A173B78AB8406AC5015094DA53F53D39B9EDB06C", -// }, -// accountID: "rMBzp8CgpE441cp5PVyA9rpVV7oT8hP3ys", -// output: "534D5400120007220008000024001ABED82A2380BF2C2019001ABED764D55920AC9391400000000000000000000000000055534400000000000A20B3C85F482532A9578DBB3950B85CA06594D165400000037E11D60068400000000000000A73008114DD76483FACDEE26E60D8A586BB58D09F27045C46DD76483FACDEE26E60D8A586BB58D09F27045C46", -// expectedErr: nil, -// }, -// } +func TestEncodeForMultisigning(t *testing.T) { + tt := []struct { + description string + input transactions.Tx + accountID string + output string + expectedErr error + }{ + { + description: "serialize tx1 for signing correctly", + input: &transactions.OfferCreate{ + BaseTx: transactions.BaseTx{ + Account: "rMBzp8CgpE441cp5PVyA9rpVV7oT8hP3ys", + TransactionType: transactions.OfferCreateTx, + Fee: 10, + Flags: types.SetFlag(524288), + Sequence: 1752792, + SigningPubKey: "03EE83BB432547885C219634A1BC407A9DB0474145D69737D09CCDC63E1DEE7FE3", + TxnSignature: "30440220143759437C04F7B61F012563AFE90D8DAFC46E86035E1D965A9CED282C97D4CE02204CFD241E86F17E011298FC1A39B63386C74306A5DE047E213B0F29EFA4571C2C", + }, + Expiration: 595640108, + OfferSequence: 1752791, + TakerGets: types.XRPCurrencyAmount(15000000000), + TakerPays: types.IssuedCurrencyAmount{ + Currency: "USD", + Issuer: "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", + Value: "7072.8", + }, + }, + accountID: "rMBzp8CgpE441cp5PVyA9rpVV7oT8hP3ys", + output: "534D5400120007220008000024001ABED82A2380BF2C2019001ABED764D55920AC9391400000000000000000000000000055534400000000000A20B3C85F482532A9578DBB3950B85CA06594D165400000037E11D60068400000000000000A73008114DD76483FACDEE26E60D8A586BB58D09F27045C46DD76483FACDEE26E60D8A586BB58D09F27045C46", + expectedErr: nil, + }, + { + description: "SigningPubKey is not present", + input: &transactions.OfferCreate{ + BaseTx: transactions.BaseTx{ + Account: "rMBzp8CgpE441cp5PVyA9rpVV7oT8hP3ys", + TransactionType: transactions.OfferCreateTx, + Fee: 10, + Flags: types.SetFlag(524288), + Sequence: 1752792, + TxnSignature: "30440220143759437C04F7B61F012563AFE90D8DAFC46E86035E1D965A9CED282C97D4CE02204CFD241E86F17E011298FC1A39B63386C74306A5DE047E213B0F29EFA4571C2C", + }, + Expiration: 595640108, + OfferSequence: 1752791, + TakerGets: types.XRPCurrencyAmount(15000000000), + TakerPays: types.IssuedCurrencyAmount{ + Currency: "USD", + Issuer: "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", + Value: "7072.8", + }, + }, + accountID: "rMBzp8CgpE441cp5PVyA9rpVV7oT8hP3ys", + output: "534D5400120007220008000024001ABED82A2380BF2C2019001ABED764D55920AC9391400000000000000000000000000055534400000000000A20B3C85F482532A9578DBB3950B85CA06594D165400000037E11D60068400000000000000A73008114DD76483FACDEE26E60D8A586BB58D09F27045C46DD76483FACDEE26E60D8A586BB58D09F27045C46", + expectedErr: nil, + }, + { + description: "SigningPubKey empty string", + input: &transactions.OfferCreate{ + BaseTx: transactions.BaseTx{ + Account: "rMBzp8CgpE441cp5PVyA9rpVV7oT8hP3ys", + TransactionType: transactions.OfferCreateTx, + Fee: 10, + Flags: types.SetFlag(524288), + Sequence: 1752792, + SigningPubKey: "", + TxnSignature: "30440220143759437C04F7B61F012563AFE90D8DAFC46E86035E1D965A9CED282C97D4CE02204CFD241E86F17E011298FC1A39B63386C74306A5DE047E213B0F29EFA4571C2C", + }, + Expiration: 595640108, + OfferSequence: 1752791, + TakerGets: types.XRPCurrencyAmount(15000000000), + TakerPays: types.IssuedCurrencyAmount{ + Currency: "USD", + Issuer: "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", + Value: "7072.8", + }, + }, + accountID: "rMBzp8CgpE441cp5PVyA9rpVV7oT8hP3ys", + output: "534D5400120007220008000024001ABED82A2380BF2C2019001ABED764D55920AC9391400000000000000000000000000055534400000000000A20B3C85F482532A9578DBB3950B85CA06594D165400000037E11D60068400000000000000A73008114DD76483FACDEE26E60D8A586BB58D09F27045C46DD76483FACDEE26E60D8A586BB58D09F27045C46", + expectedErr: nil, + }, + } -// for _, tc := range tt { -// t.Run(tc.description, func(t *testing.T) { -// got, err := EncodeForMultisigning(tc.json, tc.accountID) + for _, tc := range tt { + t.Run(tc.description, func(t *testing.T) { + got, err := EncodeForMultisigning(tc.input, tc.accountID) -// if tc.expectedErr != nil { -// require.EqualError(t, err, tc.expectedErr.Error()) -// require.Empty(t, got) -// } else { -// require.NoError(t, err) -// require.Equal(t, tc.output, got) -// } -// }) -// } -// } + if tc.expectedErr != nil { + require.EqualError(t, err, tc.expectedErr.Error()) + require.Empty(t, got) + } else { + require.NoError(t, err) + require.Equal(t, tc.output, got) + } + }) + } +} -// func TestEncodeForSigningClaim(t *testing.T) { +func TestEncodeForSigningClaim(t *testing.T) { -// tt := []struct { -// description string -// input map[string]any -// output string -// expectedErr error -// }{ -// { -// description: "successfully encode claim", -// input: map[string]any{ -// "Channel": "43904CBFCDCEC530B4037871F86EE90BF799DF8D2E0EA564BC8A3F332E4F5FB1", -// "Amount": "1000", -// }, -// output: "434C4D0043904CBFCDCEC530B4037871F86EE90BF799DF8D2E0EA564BC8A3F332E4F5FB100000000000003E8", -// expectedErr: nil, -// }, -// { -// description: "fail to encode claim - no channel", -// input: map[string]any{ -// "Amount": "1000", -// }, -// output: "", -// expectedErr: ErrSigningClaimFieldNotFound, -// }, -// { -// description: "fail to encode claim - no amount", -// input: map[string]any{ -// "Channel": "43904CBFCDCEC530B4037871F86EE90BF799DF8D2E0EA564BC8A3F332E4F5FB1", -// }, -// output: "", -// expectedErr: ErrSigningClaimFieldNotFound, -// }, -// } + tt := []struct { + description string + input transactions.PaymentChannelClaim + output string + expectedErr error + }{ + { + description: "successfully encode claim", + input: transactions.PaymentChannelClaim{ + Channel: "43904CBFCDCEC530B4037871F86EE90BF799DF8D2E0EA564BC8A3F332E4F5FB1", + Amount: 1000, + }, + output: "434C4D0043904CBFCDCEC530B4037871F86EE90BF799DF8D2E0EA564BC8A3F332E4F5FB100000000000003E8", + expectedErr: nil, + }, + { + description: "fail to encode claim - no channel", + input: transactions.PaymentChannelClaim{ + Amount: 1000, + }, + output: "", + expectedErr: ErrSigningClaimFieldNotFound, + }, + { + description: "fail to encode claim - no amount", + input: transactions.PaymentChannelClaim{ + Channel: "43904CBFCDCEC530B4037871F86EE90BF799DF8D2E0EA564BC8A3F332E4F5FB1", + }, + output: "", + expectedErr: ErrSigningClaimFieldNotFound, + }, + } -// for _, tc := range tt { -// t.Run(tc.description, func(t *testing.T) { -// got, err := EncodeForSigningClaim(tc.input) + for _, tc := range tt { + t.Run(tc.description, func(t *testing.T) { + got, err := EncodeForSigningClaim(tc.input) -// if tc.expectedErr != nil { -// require.EqualError(t, err, tc.expectedErr.Error()) -// require.Empty(t, got) -// } else { -// require.NoError(t, err) -// require.Equal(t, tc.output, got) -// } -// }) -// } -// } + if tc.expectedErr != nil { + require.EqualError(t, err, tc.expectedErr.Error()) + require.Empty(t, got) + } else { + require.NoError(t, err) + require.Equal(t, tc.output, got) + } + }) + } +} -// func TestEncodeForSigning(t *testing.T) { -// tt := []struct { -// description string -// input map[string]any -// output string -// expectedErr error -// }{ -// { -// description: "serialize STObject for signing correctly", -// input: map[string]any{ -// "Memo": map[string]any{ -// "MemoType": "04C4D46544659A2D58525043686174", -// }, -// }, -// output: "53545800EA7C0F04C4D46544659A2D58525043686174E1", -// expectedErr: nil, -// }, -// { -// description: "serialize tx1 for signing correctly", -// input: map[string]any{ -// "Account": "rMBzp8CgpE441cp5PVyA9rpVV7oT8hP3ys", -// "Expiration": 595640108, -// "Fee": "10", -// "Flags": 524288, -// "OfferSequence": 1752791, -// "Sequence": 1752792, -// "SigningPubKey": "03EE83BB432547885C219634A1BC407A9DB0474145D69737D09CCDC63E1DEE7FE3", -// "TakerGets": "15000000000", -// "TakerPays": map[string]any{ -// "currency": "USD", -// "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", -// "value": "7072.8", -// }, -// "TransactionType": "OfferCreate", -// "TxnSignature": "30440220143759437C04F7B61F012563AFE90D8DAFC46E86035E1D965A9CED282C97D4CE02204CFD241E86F17E011298FC1A39B63386C74306A5DE047E213B0F29EFA4571C2C", -// "hash": "73734B611DDA23D3F5F62E20A173B78AB8406AC5015094DA53F53D39B9EDB06C", -// }, -// output: "53545800120007220008000024001ABED82A2380BF2C2019001ABED764D55920AC9391400000000000000000000000000055534400000000000A20B3C85F482532A9578DBB3950B85CA06594D165400000037E11D60068400000000000000A732103EE83BB432547885C219634A1BC407A9DB0474145D69737D09CCDC63E1DEE7FE38114DD76483FACDEE26E60D8A586BB58D09F27045C46", -// expectedErr: nil, -// }, -// } +func TestEncodeForSigning(t *testing.T) { + tt := []struct { + description string + input transactions.Tx + output string + expectedErr error + }{ + { + description: "serialize tx1 for signing correctly", + input: &transactions.OfferCreate{ + BaseTx: transactions.BaseTx{ + Account: "rMBzp8CgpE441cp5PVyA9rpVV7oT8hP3ys", + TransactionType: transactions.OfferCreateTx, + Fee: 10, + Flags: types.SetFlag(524288), + Sequence: 1752792, + SigningPubKey: "03EE83BB432547885C219634A1BC407A9DB0474145D69737D09CCDC63E1DEE7FE3", + TxnSignature: "30440220143759437C04F7B61F012563AFE90D8DAFC46E86035E1D965A9CED282C97D4CE02204CFD241E86F17E011298FC1A39B63386C74306A5DE047E213B0F29EFA4571C2C", + }, + Expiration: 595640108, + OfferSequence: 1752791, + TakerGets: types.XRPCurrencyAmount(15000000000), + TakerPays: types.IssuedCurrencyAmount{ + Currency: "USD", + Issuer: "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", + Value: "7072.8", + }, + }, + output: "53545800120007220008000024001ABED82A2380BF2C2019001ABED764D55920AC9391400000000000000000000000000055534400000000000A20B3C85F482532A9578DBB3950B85CA06594D165400000037E11D60068400000000000000A732103EE83BB432547885C219634A1BC407A9DB0474145D69737D09CCDC63E1DEE7FE38114DD76483FACDEE26E60D8A586BB58D09F27045C46", + expectedErr: nil, + }, + } -// for _, tc := range tt { -// t.Run(tc.description, func(t *testing.T) { -// got, err := EncodeForSigning(tc.input) + for _, tc := range tt { + t.Run(tc.description, func(t *testing.T) { + got, err := EncodeForSigning(tc.input) -// if tc.expectedErr != nil { -// require.EqualError(t, err, tc.expectedErr.Error()) -// require.Empty(t, got) -// } else { -// require.NoError(t, err) -// require.Equal(t, tc.output, got) -// } -// }) -// } -// } + if tc.expectedErr != nil { + require.EqualError(t, err, tc.expectedErr.Error()) + require.Empty(t, got) + } else { + require.NoError(t, err) + require.Equal(t, tc.output, got) + } + }) + } +} diff --git a/binary-codec/types/account_id.go b/binary-codec/types/account_id.go index 6a5b05d1..69d53cf6 100644 --- a/binary-codec/types/account_id.go +++ b/binary-codec/types/account_id.go @@ -1,11 +1,17 @@ package types import ( + "errors" + addresscodec "github.com/xyield/xrpl-go/address-codec" "github.com/xyield/xrpl-go/binary-codec/serdes" "github.com/xyield/xrpl-go/model/transactions/types" ) +var ( + ErrInvalidAccountID = errors.New("invalid account ID type") +) + // AccountID struct represents an account ID. type AccountID struct{} @@ -18,7 +24,16 @@ type AccountID struct{} // AccountIDs that appear as children of special fields (Amount issuer and PathSet account) are not length-prefixed. // So in Amount and PathSet fields, don't use the length indicator 0x14. func (a *AccountID) FromJson(value any) ([]byte, error) { - _, accountID, err := addresscodec.DecodeClassicAddressToAccountID(string(value.(types.Address))) + var accountID []byte + var err error + switch v := value.(type) { + case types.Address: + _, accountID, err = addresscodec.DecodeClassicAddressToAccountID(string(v)) + case string: + _, accountID, err = addresscodec.DecodeClassicAddressToAccountID(v) + default: + return nil, ErrInvalidAccountID + } if err != nil { return nil, err diff --git a/binary-codec/types/account_id_test.go b/binary-codec/types/account_id_test.go new file mode 100644 index 00000000..41ca6f1d --- /dev/null +++ b/binary-codec/types/account_id_test.go @@ -0,0 +1,50 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/xyield/xrpl-go/model/transactions/types" +) + +func TestAccountIDFromJson(t *testing.T) { + tt := []struct { + description string + input any + expected []byte + expectedErr error + }{ + { + description: "convert address", + input: types.Address("r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59"), + expected: []byte{0x5e, 0x7b, 0x11, 0x25, 0x23, 0xf6, 0x8d, 0x2f, 0x5e, 0x87, 0x9d, 0xb4, 0xea, 0xc5, 0x1c, 0x66, 0x98, 0xa6, 0x93, 0x4}, + expectedErr: nil, + }, + { + description: "convert address from string type", + input: "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59", + expected: []byte{0x5e, 0x7b, 0x11, 0x25, 0x23, 0xf6, 0x8d, 0x2f, 0x5e, 0x87, 0x9d, 0xb4, 0xea, 0xc5, 0x1c, 0x66, 0x98, 0xa6, 0x93, 0x4}, + expectedErr: nil, + }, + { + description: "invalid type should error", + input: false, + expected: nil, + expectedErr: ErrInvalidAccountID, + }, + } + + for _, tc := range tt { + t.Run(tc.description, func(t *testing.T) { + a := &AccountID{} + got, err := a.FromJson(tc.input) + if tc.expectedErr != nil { + require.EqualError(t, err, tc.expectedErr.Error()) + require.Empty(t, got) + } else { + require.NoError(t, err) + require.Equal(t, tc.expected, got) + } + }) + } +} diff --git a/binary-codec/types/blob.go b/binary-codec/types/blob.go index 4acdc808..c7f61e76 100644 --- a/binary-codec/types/blob.go +++ b/binary-codec/types/blob.go @@ -9,13 +9,19 @@ import ( ) // ErrNoLengthPrefix error is raised when no length prefix size is given. -var ErrNoLengthPrefix error = errors.New("no length prefix size given") +var ( + ErrNoLengthPrefix error = errors.New("no length prefix size given") + ErrInvalidBlobType error = errors.New("invalid type for Blob") +) // Blob struct is used for manipulating hexadecimal data. type Blob struct{} // FromJson method for Blob converts a hexadecimal string from JSON to a byte array. func (b *Blob) FromJson(json any) ([]byte, error) { + if _, ok := json.(string); !ok { + return nil, ErrInvalidBlobType + } // Convert hexadecimal string to byte array. // Return an error if the conversion fails. v, err := hex.DecodeString(json.(string)) diff --git a/binary-codec/types/blob_test.go b/binary-codec/types/blob_test.go new file mode 100644 index 00000000..ce6fd08b --- /dev/null +++ b/binary-codec/types/blob_test.go @@ -0,0 +1,43 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestBlobFromJson(t *testing.T) { + tt := []struct { + description string + input any + expected []byte + expectedErr error + }{ + { + description: "convert string", + input: "0000000000000001", + expected: []byte{0, 0, 0, 0, 0, 0, 0, 1}, + expectedErr: nil, + }, + { + description: "invalid type should error", + input: -54, + expected: nil, + expectedErr: ErrInvalidBlobType, + }, + } + + for _, tc := range tt { + t.Run(tc.description, func(t *testing.T) { + b := &Blob{} + got, err := b.FromJson(tc.input) + if tc.expectedErr != nil { + require.EqualError(t, err, tc.expectedErr.Error()) + require.Empty(t, got) + } else { + require.NoError(t, err) + require.Equal(t, tc.expected, got) + } + }) + } +} diff --git a/binary-codec/types/hash.go b/binary-codec/types/hash.go index c41bd99b..f2f11680 100644 --- a/binary-codec/types/hash.go +++ b/binary-codec/types/hash.go @@ -1,9 +1,14 @@ package types import ( + "errors" "fmt" ) +var ( + ErrInvalidHashType = errors.New("invalid type for Hash, expected string") +) + // ErrInvalidHashLength struct is used when the hash length does not meet the expected value. type ErrInvalidHashLength struct { Expected int diff --git a/binary-codec/types/hash128.go b/binary-codec/types/hash128.go index 621ad257..c8ed725b 100644 --- a/binary-codec/types/hash128.go +++ b/binary-codec/types/hash128.go @@ -33,6 +33,8 @@ func (h *Hash128) FromJson(json any) ([]byte, error) { s = json case types.Hash128: s = string(json) + default: + return nil, ErrInvalidHashType } v, err := hex.DecodeString(s) if err != nil { diff --git a/binary-codec/types/hash128_test.go b/binary-codec/types/hash128_test.go new file mode 100644 index 00000000..9e669cb3 --- /dev/null +++ b/binary-codec/types/hash128_test.go @@ -0,0 +1,50 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/xyield/xrpl-go/model/transactions/types" +) + +func TestHash128FromJson(t *testing.T) { + tt := []struct { + description string + input any + expected []byte + expectedErr error + }{ + { + description: "convert string", + input: "00000000000000000000000000000001", + expected: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, + expectedErr: nil, + }, + { + description: "convert hash128 type", + input: types.Hash128("00000000000000000000000000000001"), + expected: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, + expectedErr: nil, + }, + { + description: "invalid type should error", + input: -54, + expected: nil, + expectedErr: ErrInvalidHashType, + }, + } + + for _, tc := range tt { + t.Run(tc.description, func(t *testing.T) { + h128 := &Hash128{} + got, err := h128.FromJson(tc.input) + if tc.expectedErr != nil { + require.EqualError(t, err, tc.expectedErr.Error()) + require.Empty(t, got) + } else { + require.NoError(t, err) + require.Equal(t, tc.expected, got) + } + }) + } +} diff --git a/binary-codec/types/hash160.go b/binary-codec/types/hash160.go index c4999d90..4abefc09 100644 --- a/binary-codec/types/hash160.go +++ b/binary-codec/types/hash160.go @@ -26,6 +26,9 @@ func (h *Hash160) getLength() int { // FromJson method for hash converts a hexadecimal string from JSON to a byte array. // It returns an error if the conversion fails or the length of the decoded byte array is not as expected. func (h *Hash160) FromJson(json any) ([]byte, error) { + if _, ok := json.(string); !ok { + return nil, ErrInvalidHashType + } v, err := hex.DecodeString(json.(string)) if err != nil { return nil, err diff --git a/binary-codec/types/hash160_test.go b/binary-codec/types/hash160_test.go new file mode 100644 index 00000000..4a824a63 --- /dev/null +++ b/binary-codec/types/hash160_test.go @@ -0,0 +1,44 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestHash160FromJson(t *testing.T) { + tt := []struct { + description string + input any + expected []byte + expectedErr error + }{ + { + description: "convert string", + input: "0000000000000000000000000000000000000001", + expected: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, + expectedErr: nil, + }, + { + description: "invalid type", + input: -54, + expected: nil, + expectedErr: ErrInvalidHashType, + }, + } + + for _, tc := range tt { + t.Run(tc.description, func(t *testing.T) { + h160 := &Hash160{} + got, err := h160.FromJson(tc.input) + if tc.expectedErr != nil { + require.EqualError(t, err, tc.expectedErr.Error()) + require.Empty(t, got) + } else { + require.NoError(t, err) + require.Equal(t, tc.expected, got) + } + }) + + } +} diff --git a/binary-codec/types/hash256.go b/binary-codec/types/hash256.go index 6e152fe4..6a7d781a 100644 --- a/binary-codec/types/hash256.go +++ b/binary-codec/types/hash256.go @@ -31,6 +31,8 @@ func (h *Hash256) FromJson(json any) ([]byte, error) { s = json case types.Hash256: s = string(json) + default: + return nil, ErrInvalidHashType } v, err := hex.DecodeString(s) if err != nil { diff --git a/binary-codec/types/hash256_test.go b/binary-codec/types/hash256_test.go new file mode 100644 index 00000000..924ebe69 --- /dev/null +++ b/binary-codec/types/hash256_test.go @@ -0,0 +1,52 @@ +package types + +import ( + "bytes" + "strings" + "testing" + + "github.com/stretchr/testify/require" + "github.com/xyield/xrpl-go/model/transactions/types" +) + +func TestHash256FromJson(t *testing.T) { + tt := []struct { + description string + input any + expected []byte + expectedErr error + }{ + { + description: "convert string", + input: strings.Repeat("0", 63) + "1", + expected: append(bytes.Repeat([]byte{0}, 31), byte(1)), + expectedErr: nil, + }, + { + description: "convert hash256 type", + input: types.Hash256(strings.Repeat("0", 63) + "1"), + expected: append(bytes.Repeat([]byte{0}, 31), byte(1)), + expectedErr: nil, + }, + { + description: "invalid type", + input: -54, + expected: nil, + expectedErr: ErrInvalidHashType, + }, + } + + for _, tc := range tt { + t.Run(tc.description, func(t *testing.T) { + h256 := &Hash256{} + got, err := h256.FromJson(tc.input) + if tc.expectedErr != nil { + require.EqualError(t, err, tc.expectedErr.Error()) + require.Empty(t, got) + } else { + require.NoError(t, err) + require.Equal(t, tc.expected, got) + } + }) + } +} diff --git a/binary-codec/types/st_object.go b/binary-codec/types/st_object.go index cc722048..db3b6dc5 100644 --- a/binary-codec/types/st_object.go +++ b/binary-codec/types/st_object.go @@ -9,10 +9,23 @@ import ( "github.com/xyield/xrpl-go/binary-codec/serdes" ) +// FieldMutation allows values to mutated before being serialized. +type FieldMutation func(any) any + +// Zero returns a FieldMutation that sets the value to its zero value. +func Zero() FieldMutation { + return func(v any) any { + return reflect.Zero(reflect.TypeOf(v)).Interface() + } +} + // STObject represents a map of serialized field instances, where each key is a field name // and the associated value is the field's value. This structure allows us to represent nested // and complex structures of the Ripple protocol. -type STObject struct{} +type STObject struct { + OnlySigning bool + Mutations map[string]FieldMutation +} // FromJson converts a JSON object into a serialized byte slice. // It works by converting the JSON object into a map of field instances (which include the field definition @@ -30,6 +43,12 @@ func (t *STObject) FromJson(json any) ([]byte, error) { return nil, err } + for k, v := range t.Mutations { + if _, ok := m[k]; ok { + m[k] = v(m[k]) + } + } + fimap, err := createFieldInstanceMapFromJson(m) if err != nil { @@ -39,7 +58,7 @@ func (t *STObject) FromJson(json any) ([]byte, error) { sk := getSortedKeys(fimap) for _, v := range sk { - if checkZero(fimap[v]) { + if checkZero(fimap[v]) && !containsKey(t.Mutations, v.FieldName) { continue } @@ -47,6 +66,10 @@ func (t *STObject) FromJson(json any) ([]byte, error) { continue } + if t.OnlySigning && !v.IsSigningField { + continue + } + st := GetSerializedType(v.Type) b, err := st.FromJson(fimap[v]) if err != nil { @@ -228,3 +251,8 @@ func checkZero(v any) bool { rv := reflect.ValueOf(v) return rv.IsZero() } + +func containsKey[T any](m map[string]T, key string) bool { + _, ok := m[key] + return ok +}