status-im/status-go

View on GitHub
protocol/transaction_validator_test.go

Summary

Maintainability
A
0 mins
Test Coverage
package protocol

import (
    "context"
    "crypto/ecdsa"
    "encoding/hex"
    "strings"
    "testing"

    "math/big"

    "github.com/stretchr/testify/suite"

    coretypes "github.com/status-im/status-go/eth-node/core/types"
    "github.com/status-im/status-go/eth-node/crypto"
    "github.com/status-im/status-go/eth-node/types"
    "github.com/status-im/status-go/protocol/common"
    "github.com/status-im/status-go/protocol/tt"
)

func padArray(bb []byte, size int) []byte {
    l := len(bb)
    if l == size {
        return bb
    }
    if l > size {
        return bb[l-size:]
    }
    tmp := make([]byte, size)
    copy(tmp[size-l:], bb)
    return tmp
}

type TransactionValidatorSuite struct {
    suite.Suite
}

func TestTransactionValidatorSuite(t *testing.T) {
    suite.Run(t, new(TransactionValidatorSuite))
}

func buildSignature(walletKey *ecdsa.PrivateKey, chatKey *ecdsa.PublicKey, hash string) ([]byte, error) {
    hashBytes, err := hex.DecodeString(hash[2:])
    if err != nil {
        return nil, err
    }
    chatKeyBytes := crypto.FromECDSAPub(chatKey)
    signatureMaterial := append(chatKeyBytes, hashBytes...)
    signatureMaterial = crypto.TextHash(signatureMaterial)
    signature, err := crypto.Sign(signatureMaterial, walletKey)
    if err != nil {
        return nil, err
    }
    signature[64] += 27
    return signature, nil
}

func buildData(fn string, to types.Address, value *big.Int) []byte {
    var data []byte
    addressBytes := make([]byte, 32)

    fnBytes, _ := hex.DecodeString(fn)
    copy(addressBytes[12:], to.Bytes())
    valueBytes := padArray(value.Bytes(), 32)

    data = append(data, fnBytes...)
    data = append(data, addressBytes...)
    data = append(data, valueBytes...)
    return data
}

func (s *TransactionValidatorSuite) TestValidateTransactions() {
    notTransferFunction := "a9059cbd"

    senderKey, err := crypto.GenerateKey()
    s.Require().NoError(err)

    senderWalletKey, err := crypto.GenerateKey()
    s.Require().NoError(err)

    myWalletKey1, err := crypto.GenerateKey()
    s.Require().NoError(err)
    myWalletKey2, err := crypto.GenerateKey()
    s.Require().NoError(err)

    senderAddress := crypto.PubkeyToAddress(senderWalletKey.PublicKey)
    myAddress1 := crypto.PubkeyToAddress(myWalletKey1.PublicKey)
    myAddress2 := crypto.PubkeyToAddress(myWalletKey2.PublicKey)

    db, err := openTestDB()
    s.Require().NoError(err)
    p := &sqlitePersistence{db: db}

    logger := tt.MustCreateTestLogger()
    validator := NewTransactionValidator([]types.Address{myAddress1, myAddress2}, p, nil, logger)

    contractString := "0x744d70fdbe2ba4cf95131626614a1763df805b9e"
    contractAddress := types.HexToAddress(contractString)

    defaultTransactionHash := "0x53edbe74408c2eeed4e5493b3aac0c006d8a14b140975f4306dd35f5e1d245bc"
    testCases := []struct {
        Name                     string
        Valid                    bool
        AccordingToSpec          bool
        Error                    bool
        Transaction              coretypes.Message
        OverrideSignatureChatKey *ecdsa.PublicKey
        OverrideTransactionHash  string
        Parameters               *common.CommandParameters
        WalletKey                *ecdsa.PrivateKey
        From                     *ecdsa.PublicKey
    }{
        {
            Name:            "valid eth transfer to any address",
            Valid:           true,
            AccordingToSpec: true,
            Transaction: coretypes.NewMessage(
                senderAddress,
                &myAddress1,
                1,
                big.NewInt(int64(23)),
                0,
                nil,
                nil,
                false,
            ),
            Parameters: &common.CommandParameters{
                Value: "23",
            },
            WalletKey: senderWalletKey,
            From:      &senderKey.PublicKey,
        },
        {
            Name:            "valid eth transfer to specific address",
            Valid:           true,
            AccordingToSpec: true,
            Transaction: coretypes.NewMessage(
                senderAddress,
                &myAddress1,
                1,
                big.NewInt(int64(23)),
                0,
                nil,
                nil,
                false,
            ),
            Parameters: &common.CommandParameters{
                Value:   "23",
                Address: strings.ToLower(myAddress1.Hex()),
            },
            WalletKey: senderWalletKey,
            From:      &senderKey.PublicKey,
        },
        {
            Name: "invalid eth transfer, not includes pk of the chat in signature",
            Transaction: coretypes.NewMessage(
                senderAddress,
                &myAddress1,
                1,
                big.NewInt(int64(23)),
                0,
                nil,
                nil,
                false,
            ),
            Parameters: &common.CommandParameters{
                Value:   "23",
                Address: strings.ToLower(myAddress1.Hex()),
            },
            WalletKey:                senderWalletKey,
            OverrideSignatureChatKey: &senderWalletKey.PublicKey,
            From:                     &senderKey.PublicKey,
        },
        {
            Name: "invalid eth transfer, not signed with the wallet key",
            Transaction: coretypes.NewMessage(
                senderAddress,
                &myAddress1,
                1,
                big.NewInt(int64(23)),
                0,
                nil,
                nil,
                false,
            ),
            Parameters: &common.CommandParameters{
                Value:   "23",
                Address: strings.ToLower(myAddress1.Hex()),
            },
            WalletKey: senderKey,
            From:      &senderKey.PublicKey,
        },
        {
            Name: "invalid eth transfer, wrong signature transaction hash",
            Transaction: coretypes.NewMessage(
                senderAddress,
                &myAddress1,
                1,
                big.NewInt(int64(23)),
                0,
                nil,
                nil,
                false,
            ),
            OverrideTransactionHash: "0xdd9202df5e2f3611b5b6b716aef2a3543cc0bdd7506f50926e0869b83c8383b9",
            Parameters: &common.CommandParameters{
                Value: "23",
            },
            WalletKey: senderWalletKey,
            From:      &senderKey.PublicKey,
        },

        {
            Name: "invalid eth transfer, we own the wallet but not as specified",
            Transaction: coretypes.NewMessage(
                senderAddress,
                &myAddress1,
                1,
                big.NewInt(int64(23)),
                0,
                nil,
                nil,
                false,
            ),
            Parameters: &common.CommandParameters{
                Value:   "23",
                Address: strings.ToLower(myAddress2.Hex()),
            },
            WalletKey: senderWalletKey,
            From:      &senderKey.PublicKey,
        },
        {
            Name: "invalid eth transfer, not our wallet",
            Transaction: coretypes.NewMessage(
                senderAddress,
                &senderAddress,
                1,
                big.NewInt(int64(23)),
                0,
                nil,
                nil,
                false,
            ),
            Parameters: &common.CommandParameters{
                Value: "23",
            },
            WalletKey: senderWalletKey,
            From:      &senderKey.PublicKey,
        },
        {
            Name:  "valid eth transfer, but not according to spec, wrong amount",
            Valid: true,
            Transaction: coretypes.NewMessage(
                senderAddress,
                &myAddress1,
                1,
                big.NewInt(int64(20)),
                0,
                nil,
                nil,
                false,
            ),
            Parameters: &common.CommandParameters{
                Value: "23",
            },
            WalletKey: senderWalletKey,
            From:      &senderKey.PublicKey,
        },
        {
            Name:            "valid token transfer to any address",
            Valid:           true,
            AccordingToSpec: true,
            Transaction: coretypes.NewMessage(
                senderAddress,
                &contractAddress,
                1,
                big.NewInt(int64(0)),
                0,
                nil,
                buildData(transferFunction, myAddress1, big.NewInt(int64(23))),
                false,
            ),
            Parameters: &common.CommandParameters{
                Contract: contractString,
                Value:    "23",
            },
            WalletKey: senderWalletKey,
            From:      &senderKey.PublicKey,
        },
        {
            Name:            "valid token transfer to a specific address",
            Valid:           true,
            AccordingToSpec: true,
            Transaction: coretypes.NewMessage(
                senderAddress,
                &contractAddress,
                1,
                big.NewInt(int64(0)),
                0,
                nil,
                buildData(transferFunction, myAddress1, big.NewInt(int64(23))),
                false,
            ),
            Parameters: &common.CommandParameters{
                Contract: contractString,
                Address:  strings.ToLower(myAddress1.Hex()),
                Value:    "23",
            },
            WalletKey: senderWalletKey,
            From:      &senderKey.PublicKey,
        },
        {
            Name:            "valid token transfer, not according to spec because of amount",
            Valid:           true,
            AccordingToSpec: false,
            Transaction: coretypes.NewMessage(
                senderAddress,
                &contractAddress,
                1,
                big.NewInt(int64(0)),
                0,
                nil,
                buildData(transferFunction, myAddress1, big.NewInt(int64(13))),
                false,
            ),
            Parameters: &common.CommandParameters{
                Contract: contractString,
                Value:    "23",
            },
            WalletKey: senderWalletKey,
            From:      &senderKey.PublicKey,
        },
        {
            Name: "invalid token transfer, wrong contract",
            Transaction: coretypes.NewMessage(
                senderAddress,
                &senderAddress,
                1,
                big.NewInt(int64(0)),
                0,
                nil,
                buildData(transferFunction, myAddress1, big.NewInt(int64(23))),
                false,
            ),
            Parameters: &common.CommandParameters{
                Contract: contractString,
                Address:  strings.ToLower(myAddress1.Hex()),
                Value:    "23",
            },
            WalletKey: senderWalletKey,
            From:      &senderKey.PublicKey,
        },

        {
            Name: "invalid token transfer, not an address I own",
            Transaction: coretypes.NewMessage(
                senderAddress,
                &contractAddress,
                1,
                big.NewInt(int64(0)),
                0,
                nil,
                buildData(transferFunction, myAddress1, big.NewInt(int64(23))),
                false,
            ),
            Parameters: &common.CommandParameters{
                Contract: contractString,
                Address:  strings.ToLower(senderAddress.Hex()),
                Value:    "23",
            },
            WalletKey: senderWalletKey,
            From:      &senderKey.PublicKey,
        },

        {
            Name: "invalid token transfer, not the specified address",
            Transaction: coretypes.NewMessage(
                senderAddress,
                &contractAddress,
                1,
                big.NewInt(int64(0)),
                0,
                nil,
                buildData(transferFunction, myAddress2, big.NewInt(int64(23))),
                false,
            ),
            Parameters: &common.CommandParameters{
                Contract: contractString,
                Address:  strings.ToLower(myAddress1.Hex()),
                Value:    "23",
            },
            WalletKey: senderWalletKey,
            From:      &senderKey.PublicKey,
        },
        {
            Name: "invalid token transfer, wrong fn",
            Transaction: coretypes.NewMessage(
                senderAddress,
                &contractAddress,
                1,
                big.NewInt(int64(0)),
                0,
                nil,
                buildData(notTransferFunction, myAddress1, big.NewInt(int64(23))),
                false,
            ),
            Parameters: &common.CommandParameters{
                Contract: contractString,
                Value:    "23",
            },
            WalletKey: senderWalletKey,
            From:      &senderKey.PublicKey,
        },
    }

    for _, tc := range testCases {
        s.Run(tc.Name, func() {
            tc.Parameters.TransactionHash = defaultTransactionHash
            signatureTransactionHash := defaultTransactionHash
            signatureChatKey := tc.From
            if tc.OverrideTransactionHash != "" {
                signatureTransactionHash = tc.OverrideTransactionHash
            }
            if tc.OverrideSignatureChatKey != nil {
                signatureChatKey = tc.OverrideSignatureChatKey
            }
            signature, err := buildSignature(tc.WalletKey, signatureChatKey, signatureTransactionHash)
            s.Require().NoError(err)
            tc.Parameters.Signature = signature

            response, err := validator.validateTransaction(context.Background(), tc.Transaction, tc.Parameters, tc.From)
            if tc.Error {
                s.Error(err)
                return
            }
            s.Require().NoError(err)
            s.Equal(tc.AccordingToSpec, response.AccordingToSpec)
            s.Equal(tc.Valid, response.Valid)
        })
    }

}