diff --git a/Makefile b/Makefile index 474757b0..5176c067 100644 --- a/Makefile +++ b/Makefile @@ -45,8 +45,7 @@ test: ${TEST_SCRIPT} test-cover: - ${TEST_SCRIPT} -coverprofile=c.out -covermode=count - ${GOVERALLS_CMD} -coverprofile=c.out -repotoken ${COVERALLS_TOKEN} + if [ "${COVERALLS_TOKEN}" ]; then ${TEST_SCRIPT} -coverprofile=c.out -covermode=count; ${GOVERALLS_CMD} -coverprofile=c.out -repotoken ${COVERALLS_TOKEN}; fi add-license: ${ADDLICENCE_SCRIPT} . diff --git a/parser/match_operations.go b/parser/match_operations.go index 68ce25f0..3d6239d3 100644 --- a/parser/match_operations.go +++ b/parser/match_operations.go @@ -135,6 +135,11 @@ type Descriptions struct { // will error if all groups of operations aren't opposites. OppositeAmounts [][]int + // EqualAddresses are specified using the operation indicies of + // OperationDescriptions to handle out of order matches. MatchOperations + // will error if all groups of operations addresses aren't equal. + EqualAddresses [][]int + // ErrUnmatched indicates that an error should be returned // if all operations cannot be matched to a description. ErrUnmatched bool @@ -343,6 +348,33 @@ func oppositeAmounts(a *types.Operation, b *types.Operation) error { return nil } +// equalAddresses returns an error if a slice of operations do not have +// equal addresses. +func equalAddresses(ops []*types.Operation) error { + if len(ops) <= 1 { + return fmt.Errorf("cannot check equality of %d operations", len(ops)) + } + + base := "" + + for _, op := range ops { + if op.Account == nil { + return fmt.Errorf("account is nil") + } + + if len(base) == 0 { + base = op.Account.Address + continue + } + + if base != op.Account.Address { + return fmt.Errorf("%s is not equal to %s", base, op.Account.Address) + } + } + + return nil +} + func matchIndexValid(matches []*Match, index int) error { if index >= len(matches) { return fmt.Errorf( @@ -360,27 +392,39 @@ func matchIndexValid(matches []*Match, index int) error { return nil } -// comparisonMatch ensures collections of *types.Operations -// have either equal or opposite amounts. -func comparisonMatch( - descriptions *Descriptions, - matches []*Match, -) error { - for _, amountMatch := range descriptions.EqualAmounts { +func checkOps(requests [][]int, matches []*Match, valid func([]*types.Operation) error) error { + for _, batch := range requests { ops := []*types.Operation{} - for _, reqIndex := range amountMatch { + for _, reqIndex := range batch { if err := matchIndexValid(matches, reqIndex); err != nil { - return fmt.Errorf("%w: equal amounts comparison error", err) + return fmt.Errorf("%w: index %d not valid", err, reqIndex) } ops = append(ops, matches[reqIndex].Operations...) } - if err := equalAmounts(ops); err != nil { - return fmt.Errorf("%w: operations not equal", err) + if err := valid(ops); err != nil { + return fmt.Errorf("%w operations not valid", err) } } + return nil +} + +// comparisonMatch ensures collections of *types.Operations +// have either equal or opposite amounts. +func comparisonMatch( + descriptions *Descriptions, + matches []*Match, +) error { + if err := checkOps(descriptions.EqualAmounts, matches, equalAmounts); err != nil { + return fmt.Errorf("%w: operation amounts not equal", err) + } + + if err := checkOps(descriptions.EqualAddresses, matches, equalAddresses); err != nil { + return fmt.Errorf("%w: operation addresses not equal", err) + } + for _, amountMatch := range descriptions.OppositeAmounts { if len(amountMatch) != oppositesLength { // cannot have opposites without exactly 2 return fmt.Errorf("cannot check opposites of %d operations", len(amountMatch)) diff --git a/parser/match_operations_test.go b/parser/match_operations_test.go index b1c9fcfe..2f23bc64 100644 --- a/parser/match_operations_test.go +++ b/parser/match_operations_test.go @@ -104,6 +104,49 @@ func TestMatchOperations(t *testing.T) { }, err: false, }, + "simple transfer (with missing account error)": { + operations: []*types.Operation{ + { + Account: &types.AccountIdentifier{ + Address: "addr2", + }, + Amount: &types.Amount{ + Value: "100", + }, + }, + { + Amount: &types.Amount{ + Value: "-100", + }, + }, + }, + descriptions: &Descriptions{ + OppositeAmounts: [][]int{{0, 1}}, + EqualAddresses: [][]int{{0, 1}}, + OperationDescriptions: []*OperationDescription{ + { + Account: &AccountDescription{ + Exists: false, + }, + Amount: &AmountDescription{ + Exists: true, + Sign: NegativeAmountSign, + }, + }, + { + Account: &AccountDescription{ + Exists: true, + }, + Amount: &AmountDescription{ + Exists: true, + Sign: PositiveAmountSign, + }, + }, + }, + }, + matches: nil, + err: true, + }, "simple transfer (check type)": { operations: []*types.Operation{ { @@ -498,7 +541,7 @@ func TestMatchOperations(t *testing.T) { matches: nil, err: true, }, - "simple transfer (with sender metadata)": { + "simple transfer (with sender metadata) and non-equal addresses": { operations: []*types.Operation{ { Account: &types.AccountIdentifier{ @@ -526,6 +569,68 @@ func TestMatchOperations(t *testing.T) { }, descriptions: &Descriptions{ OppositeAmounts: [][]int{{0, 1}}, + EqualAddresses: [][]int{{0, 1}}, + OperationDescriptions: []*OperationDescription{ + { + Account: &AccountDescription{ + Exists: true, + SubAccountExists: true, + SubAccountAddress: "sub", + SubAccountMetadataKeys: []*MetadataDescription{ + { + Key: "validator", + ValueKind: reflect.String, + }, + }, + }, + Amount: &AmountDescription{ + Exists: true, + Sign: NegativeAmountSign, + }, + }, + { + Account: &AccountDescription{ + Exists: true, + }, + Amount: &AmountDescription{ + Exists: true, + Sign: PositiveAmountSign, + }, + }, + }, + }, + matches: nil, + err: true, + }, + "simple transfer (with sender metadata)": { + operations: []*types.Operation{ + { + Account: &types.AccountIdentifier{ + Address: "addr1", + }, + Amount: &types.Amount{ + Value: "100", + }, + }, + {}, // extra op ignored + { + Account: &types.AccountIdentifier{ + Address: "addr1", + SubAccount: &types.SubAccountIdentifier{ + Address: "sub", + Metadata: map[string]interface{}{ + "validator": "10", + }, + }, + }, + Amount: &types.Amount{ + Value: "-100", + }, + }, + }, + descriptions: &Descriptions{ + OppositeAmounts: [][]int{{0, 1}}, + EqualAddresses: [][]int{{0, 1}}, OperationDescriptions: []*OperationDescription{ { Account: &AccountDescription{ @@ -579,7 +684,7 @@ func TestMatchOperations(t *testing.T) { Operations: []*types.Operation{ { Account: &types.AccountIdentifier{ - Address: "addr2", + Address: "addr1", }, Amount: &types.Amount{ Value: "100",