Skip to content
Open
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
280 changes: 127 additions & 153 deletions chain/transaction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"github.com/ava-labs/avalanchego/ids"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"

"github.com/ava-labs/hypersdk/auth"
"github.com/ava-labs/hypersdk/chain"
Expand All @@ -26,158 +27,102 @@ var (
_ chain.Action = (*action2)(nil)
)

type abstractMockAction struct{}

func (*abstractMockAction) ComputeUnits(chain.Rules) uint64 {
panic("unimplemented")
}

func (*abstractMockAction) Execute(_ context.Context, _ chain.Rules, _ state.Mutable, _ int64, _ codec.Address, _ ids.ID) (codec.Typed, error) {
panic("unimplemented")
}

func (*abstractMockAction) StateKeys(_ codec.Address, _ ids.ID) state.Keys {
panic("unimplemented")
}

func (*abstractMockAction) ValidRange(chain.Rules) (start int64, end int64) {
panic("unimplemented")
}

type mockTransferAction struct {
abstractMockAction
To codec.Address `serialize:"true" json:"to"`
Value uint64 `serialize:"true" json:"value"`
Memo []byte `serialize:"true" json:"memo"`
}

func (*mockTransferAction) GetTypeID() uint8 {
return 111
}

type action2 struct {
abstractMockAction
A uint64 `serialize:"true" json:"a"`
B uint64 `serialize:"true" json:"b"`
}

func (*action2) GetTypeID() uint8 {
return 222
func TestTransactionTestSuite(t *testing.T) {
suite.Run(t, new(TransactionTestSuite))
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure testify suite adds much value to the tests beyond what we can do with a single helper function at the start of each test and would prefer not to introduce it here as the only test where we start using this tool.

Could we instead share test setup code via a single setup helper function that returns the fields that have currently been added to TransactionTestSuite ?

}

func unmarshalTransfer(p *codec.Packer) (chain.Action, error) {
var transfer mockTransferAction
err := codec.LinearCodec.UnmarshalFrom(p.Packer, &transfer)
return &transfer, err
}
type TransactionTestSuite struct {
suite.Suite

func unmarshalAction2(p *codec.Packer) (chain.Action, error) {
var action action2
err := codec.LinearCodec.UnmarshalFrom(p.Packer, &action)
return &action, err
privateKey ed25519.PrivateKey
factory *auth.ED25519Factory
actionCodec *codec.TypeParser[chain.Action]
authCodec *codec.TypeParser[chain.Auth]
}

func TestJSONMarshalUnmarshal(t *testing.T) {
require := require.New(t)

txData := chain.TransactionData{
Base: &chain.Base{
Timestamp: 1724315246000,
ChainID: [32]byte{1, 2, 3, 4, 5, 6, 7},
MaxFee: 1234567,
},
Actions: []chain.Action{
&mockTransferAction{
To: codec.Address{1, 2, 3, 4},
Value: 4,
Memo: []byte("hello"),
},
&mockTransferAction{
To: codec.Address{4, 5, 6, 7},
Value: 123,
Memo: []byte("world"),
},
&action2{
A: 2,
B: 4,
},
},
}
priv, err := ed25519.GeneratePrivateKey()
func (s *TransactionTestSuite) SetupTest() {
require := require.New(s.T())
var err error
s.privateKey, err = ed25519.GeneratePrivateKey()
require.NoError(err)
factory := auth.NewED25519Factory(priv)
s.factory = auth.NewED25519Factory(s.privateKey)

actionCodec := codec.NewTypeParser[chain.Action]()
authCodec := codec.NewTypeParser[chain.Auth]()

err = actionCodec.Register(&mockTransferAction{}, unmarshalTransfer)
s.actionCodec = codec.NewTypeParser[chain.Action]()
s.authCodec = codec.NewTypeParser[chain.Auth]()
err = s.authCodec.Register(&auth.ED25519{}, auth.UnmarshalED25519)
require.NoError(err)
err = actionCodec.Register(&action2{}, unmarshalAction2)
err = s.actionCodec.Register(&mockTransferAction{}, unmarshalTransfer)
require.NoError(err)
err = authCodec.Register(&auth.ED25519{}, auth.UnmarshalED25519)
err = s.actionCodec.Register(&action2{}, unmarshalAction2)
require.NoError(err)
}

func (s *TransactionTestSuite) TestJSONMarshalUnmarshal() {
require := require.New(s.T())
tx := getTransactionData()

signedTx, err := txData.Sign(factory)
signedTx, err := tx.Sign(s.factory)
require.NoError(err)

b, err := json.Marshal(signedTx)
require.NoError(err)

parser := chaintest.NewParser(nil, actionCodec, authCodec, nil)

parser := chaintest.NewParser(nil, s.actionCodec, s.authCodec, nil)
var txFromJSON chain.Transaction
err = txFromJSON.UnmarshalJSON(b, parser)
require.NoError(err)
require.Equal(signedTx.Bytes(), txFromJSON.Bytes())
}

// TestMarshalUnmarshal roughly validates that a transaction packs and unpacks correctly
func TestMarshalUnmarshal(t *testing.T) {
require := require.New(t)

tx := chain.TransactionData{
Base: &chain.Base{
Timestamp: 1724315246000,
ChainID: [32]byte{1, 2, 3, 4, 5, 6, 7},
MaxFee: 1234567,
},
Actions: []chain.Action{
&mockTransferAction{
To: codec.Address{1, 2, 3, 4},
Value: 4,
Memo: []byte("hello"),
},
&mockTransferAction{
To: codec.Address{4, 5, 6, 7},
Value: 123,
Memo: []byte("world"),
},
&action2{
A: 2,
B: 4,
},
},
}

priv, err := ed25519.GeneratePrivateKey()
// TestMarshalUnmarshalTransactionData roughly validates that a transaction data packs and unpacks correctly.
func (s *TransactionTestSuite) TestMarshalUnmarshalTransactionData() {
require := require.New(s.T())
tx := getTransactionData()
writerPacker := codec.NewWriter(0, consts.NetworkSizeLimit)
err := tx.Marshal(writerPacker)
require.NoError(err)
txDataBytes, err := tx.UnsignedBytes()
require.NoError(err)
require.Equal(writerPacker.Bytes(), txDataBytes)
readerPacker := codec.NewReader(writerPacker.Bytes(), consts.NetworkSizeLimit)
unmarshaledTxData, err := chain.UnmarshalTxData(readerPacker, s.actionCodec)
require.NoError(err)
factory := auth.NewED25519Factory(priv)
require.Equal(tx, *unmarshaledTxData)
}

actionCodec := codec.NewTypeParser[chain.Action]()
authCodec := codec.NewTypeParser[chain.Auth]()
// TestMarshalUnmarshalTransaction roughly validates that a transaction packs and unpacks correctly.
func (s *TransactionTestSuite) TestMarshalUnmarshalTransaction() {
require := require.New(s.T())
tx := getTransactionData()
// call UnsignedBytes so that the "unsignedBytes" field would get populated.
originalUnsignedTxBytes, err := tx.UnsignedBytes()
require.NoError(err)

err = authCodec.Register(&auth.ED25519{}, auth.UnmarshalED25519)
signedTx, err := tx.Sign(s.factory)
require.NoError(err)
writerPacker := codec.NewWriter(0, consts.NetworkSizeLimit)
err = signedTx.Marshal(writerPacker)
require.NoError(err)
err = actionCodec.Register(&mockTransferAction{}, unmarshalTransfer)
require.Equal(signedTx.ID(), utils.ToID(writerPacker.Bytes()))
require.Equal(signedTx.Bytes(), writerPacker.Bytes())
unsignedTxBytes, err := signedTx.UnsignedBytes()
require.NoError(err)
err = actionCodec.Register(&action2{}, unmarshalAction2)
require.Equal(unsignedTxBytes, originalUnsignedTxBytes)
require.Len(unsignedTxBytes, 168)

readerPacker := codec.NewReader(writerPacker.Bytes(), consts.NetworkSizeLimit)
unmarshaledTx, err := chain.UnmarshalTx(readerPacker, s.actionCodec, s.authCodec)
require.NoError(err)
require.Equal(writerPacker.Bytes(), unmarshaledTx.Bytes())
}

func (s *TransactionTestSuite) TestSign() {
require := require.New(s.T())
tx := getTransactionData()

// call UnsignedBytes so that the "unsignedBytes" field would get populated.
txBeforeSignBytes, err := tx.UnsignedBytes()
require.NoError(err)

signedTx, err := tx.Sign(factory)
signedTx, err := tx.Sign(s.factory)
require.NoError(err)
unsignedTxAfterSignBytes, err := signedTx.TransactionData.UnsignedBytes()
require.NoError(err)
Expand All @@ -187,24 +132,26 @@ func TestMarshalUnmarshal(t *testing.T) {
for i, action := range signedTx.Actions {
require.Equal(tx.Actions[i], action)
}
writerPacker := codec.NewWriter(0, consts.NetworkSizeLimit)
err = signedTx.Marshal(writerPacker)
require.NoError(err)
require.Equal(signedTx.ID(), utils.ToID(writerPacker.Bytes()))
require.Equal(signedTx.Bytes(), writerPacker.Bytes())
}

unsignedTxBytes, err := signedTx.UnsignedBytes()
require.NoError(err)
originalUnsignedTxBytes, err := tx.UnsignedBytes()
func (s *TransactionTestSuite) TestSignRawActionBytesTx() {
require := require.New(s.T())
tx := getTransactionData()

signedTx, err := tx.Sign(s.factory)
require.NoError(err)

require.Equal(unsignedTxBytes, originalUnsignedTxBytes)
require.Len(unsignedTxBytes, 168)
p := codec.NewWriter(0, consts.NetworkSizeLimit)
require.NoError(signedTx.Actions.MarshalInto(p))
actionsBytes := p.Bytes()
rawSignedTxBytes, err := chain.SignRawActionBytesTx(tx.Base, actionsBytes, s.factory)
require.NoError(err)
require.Equal(signedTx.Bytes(), rawSignedTxBytes)
}

func TestSignRawActionBytesTx(t *testing.T) {
require := require.New(t)
tx := chain.TransactionData{
// getTransactionData returns a default TransactionData struct used by the tests.
func getTransactionData() chain.TransactionData {
return chain.TransactionData{
Base: &chain.Base{
Timestamp: 1724315246000,
ChainID: [32]byte{1, 2, 3, 4, 5, 6, 7},
Expand All @@ -227,28 +174,55 @@ func TestSignRawActionBytesTx(t *testing.T) {
},
},
}
}

priv, err := ed25519.GeneratePrivateKey()
require.NoError(err)
factory := auth.NewED25519Factory(priv)
type abstractMockAction struct{}

actionCodec := codec.NewTypeParser[chain.Action]()
authCodec := codec.NewTypeParser[chain.Auth]()
func (*abstractMockAction) ComputeUnits(chain.Rules) uint64 {
panic("unimplemented")
}

err = authCodec.Register(&auth.ED25519{}, auth.UnmarshalED25519)
require.NoError(err)
err = actionCodec.Register(&mockTransferAction{}, unmarshalTransfer)
require.NoError(err)
err = actionCodec.Register(&action2{}, unmarshalAction2)
require.NoError(err)
func (*abstractMockAction) Execute(_ context.Context, _ chain.Rules, _ state.Mutable, _ int64, _ codec.Address, _ ids.ID) (codec.Typed, error) {
panic("unimplemented")
}

signedTx, err := tx.Sign(factory)
require.NoError(err)
func (*abstractMockAction) StateKeys(_ codec.Address, _ ids.ID) state.Keys {
panic("unimplemented")
}

p := codec.NewWriter(0, consts.NetworkSizeLimit)
require.NoError(signedTx.Actions.MarshalInto(p))
actionsBytes := p.Bytes()
rawSignedTxBytes, err := chain.SignRawActionBytesTx(tx.Base, actionsBytes, factory)
require.NoError(err)
require.Equal(signedTx.Bytes(), rawSignedTxBytes)
func (*abstractMockAction) ValidRange(chain.Rules) (start int64, end int64) {
panic("unimplemented")
}

type mockTransferAction struct {
abstractMockAction
To codec.Address `serialize:"true" json:"to"`
Value uint64 `serialize:"true" json:"value"`
Memo []byte `serialize:"true" json:"memo"`
}

func (*mockTransferAction) GetTypeID() uint8 {
return 111
}

type action2 struct {
abstractMockAction
A uint64 `serialize:"true" json:"a"`
B uint64 `serialize:"true" json:"b"`
}

func (*action2) GetTypeID() uint8 {
return 222
}

func unmarshalTransfer(p *codec.Packer) (chain.Action, error) {
var transfer mockTransferAction
err := codec.LinearCodec.UnmarshalFrom(p.Packer, &transfer)
return &transfer, err
}

func unmarshalAction2(p *codec.Packer) (chain.Action, error) {
var action action2
err := codec.LinearCodec.UnmarshalFrom(p.Packer, &action)
return &action, err
}