Repository: trustwallet/blockatlas
Pull request #929 (2020-02-26 03:49:01)

Add transaction direction
Add transaction direction, which was outgoing by default + tests

kolya182: > Let's check that `len(event.Topics[1]) > 3` before `event.Topics[1]` or `event.Topics[2]` to prevent any possibilty to have an error

We do have it already https://github.com/trustwallet/blockatlas/blob/master/platform/vechain/transaction.go#L89

Patch:
diff --git a/platform/vechain/transaction.go b/platform/vechain/transaction.go
index 119049b..5d3edfa 100644
--- a/platform/vechain/transaction.go
+++ b/platform/vechain/transaction.go
@@ -77,8 +77,6 @@ func (p *Platform) NormalizeTokenTransaction(srcTx Tx, receipt TxReceipt) (block
 		return blockatlas.TxPage{}, errors.E("NormalizeBlockTransaction: Clauses not found", errors.Params{"tx": srcTx})
 	}
 
-	origin := address.EIP55Checksum(blockatlas.GetValidParameter(srcTx.Origin, srcTx.Meta.TxOrigin))
-
 	fee, err := numbers.HexToDecimal(receipt.Paid)
 	if err != nil {
 		return blockatlas.TxPage{}, err
@@ -90,31 +88,40 @@ func (p *Platform) NormalizeTokenTransaction(srcTx Tx, receipt TxReceipt) (block
 			continue
 		}
 		event := output.Events[0] // TODO add support for multisend
-		to := address.EIP55Checksum(event.Address)
 		value, err := numbers.HexToDecimal(event.Data)
 		if err != nil {
 			continue
 		}
+		originSender := address.EIP55Checksum(blockatlas.GetValidParameter(srcTx.Origin, srcTx.Meta.TxOrigin))
+		originReceiver := address.EIP55Checksum(event.Address)
+		topicsFrom := address.EIP55Checksum(getRecipientAddress(event.Topics[1]))
+		topicsTo := address.EIP55Checksum(getRecipientAddress(event.Topics[2]))
+
+		direction, err := getTokenTransactionDirectory(originSender, topicsFrom, topicsTo)
+		if err != nil {
+			continue
+		}
 
 		txs = append(txs, blockatlas.Tx{
-			ID:     srcTx.Id,
-			Coin:   p.Coin().ID,
-			From:   origin,
-			To:     to,
-			Fee:    blockatlas.Amount(fee),
-			Date:   srcTx.Meta.BlockTimestamp,
-			Type:   blockatlas.TxTokenTransfer,
-			Block:  srcTx.Meta.BlockNumber,
-			Status: blockatlas.StatusCompleted,
+			ID:        srcTx.Id,
+			Coin:      p.Coin().ID,
+			From:      originSender,
+			To:        originReceiver,
+			Fee:       blockatlas.Amount(fee),
+			Date:      srcTx.Meta.BlockTimestamp,
+			Type:      blockatlas.TxTokenTransfer,
+			Block:     srcTx.Meta.BlockNumber,
+			Status:    blockatlas.StatusCompleted,
+			Direction: direction,
 			Meta: blockatlas.TokenTransfer{
 				// the only supported Token on VeChain is its Gas token
 				Name:     gasTokenName,
-				TokenID:  to,
+				TokenID:  originReceiver,
 				Value:    blockatlas.Amount(value),
 				Symbol:   gasTokenSymbol,
 				Decimals: gasTokenDecimals,
-				From:     origin,
-				To:       address.EIP55Checksum(getRecipientAddress(event.Topics[2])),
+				From:     originSender,
+				To:       topicsTo,
 			},
 		})
 	}
@@ -137,7 +144,7 @@ func (p *Platform) GetTxsByAddress(address string) (blockatlas.TxPage, error) {
 		if err != nil {
 			continue
 		}
-		tx, err := p.NormalizeTransaction(t, trxId)
+		tx, err := p.NormalizeTransaction(t, trxId, address)
 		if err != nil {
 			continue
 		}
@@ -146,24 +153,33 @@ func (p *Platform) GetTxsByAddress(address string) (blockatlas.TxPage, error) {
 	return txs, nil
 }
 
-func (p *Platform) NormalizeTransaction(srcTx LogTransfer, trxId Tx) (blockatlas.Tx, error) {
+func (p *Platform) NormalizeTransaction(srcTx LogTransfer, trxId Tx, addr string) (blockatlas.Tx, error) {
 	value, err := numbers.HexToDecimal(srcTx.Amount)
 	if err != nil {
 		return blockatlas.Tx{}, err
 	}
 
 	fee := strconv.Itoa(trxId.Gas)
+	sender := address.EIP55Checksum(srcTx.Sender)
+	recipient := address.EIP55Checksum(srcTx.Recipient)
+	addrChecksum := address.EIP55Checksum(addr)
+
+	directory, err := getTransferDirectory(sender, recipient, addrChecksum)
+	if err != nil {
+		return blockatlas.Tx{}, err
+	}
 
 	return blockatlas.Tx{
-		ID:     srcTx.Meta.TxId,
-		Coin:   p.Coin().ID,
-		From:   address.EIP55Checksum(srcTx.Sender),
-		To:     address.EIP55Checksum(srcTx.Recipient),
-		Fee:    blockatlas.Amount(fee),
-		Date:   srcTx.Meta.BlockTimestamp,
-		Type:   blockatlas.TxTransfer,
-		Block:  srcTx.Meta.BlockNumber,
-		Status: blockatlas.StatusCompleted,
+		ID:        srcTx.Meta.TxId,
+		Coin:      p.Coin().ID,
+		From:      sender,
+		To:        recipient,
+		Fee:       blockatlas.Amount(fee),
+		Date:      srcTx.Meta.BlockTimestamp,
+		Type:      blockatlas.TxTransfer,
+		Block:     srcTx.Meta.BlockNumber,
+		Direction: directory,
+		Status:    blockatlas.StatusCompleted,
 		Meta: blockatlas.Transfer{
 			Value:    blockatlas.Amount(value),
 			Symbol:   p.Coin().Symbol,
@@ -185,3 +201,29 @@ func hexToInt(hex string) (uint64, error) {
 func getRecipientAddress(hex string) string {
 	return "0x" + hex[len(hex)-40:]
 }
+
+func getTokenTransactionDirectory(originSender, topicsFrom, topicsTo string) (dir blockatlas.Direction, err error) {
+	if originSender == topicsFrom && originSender == topicsTo {
+		return blockatlas.DirectionSelf, nil
+	}
+	if originSender == topicsFrom && originSender != topicsTo {
+		return blockatlas.DirectionIncoming, nil
+	}
+	if originSender == topicsTo && originSender != topicsFrom {
+		return blockatlas.DirectionOutgoing, nil
+	}
+	return "", errors.E("Unknown direction")
+}
+
+func getTransferDirectory(sender, recipient, addr string) (dir blockatlas.Direction, err error) {
+	if sender == addr && recipient == addr {
+		return blockatlas.DirectionSelf, nil
+	}
+	if sender == addr && recipient != addr {
+		return blockatlas.DirectionOutgoing, nil
+	}
+	if recipient == addr && sender != addr {
+		return blockatlas.DirectionIncoming, nil
+	}
+	return "", errors.E("Unknown direction")
+}


Test patch:
diff --git a/platform/vechain/transaction_test.go b/platform/vechain/transaction_test.go
index 0383c37..43fd3bb 100644
--- a/platform/vechain/transaction_test.go
+++ b/platform/vechain/transaction_test.go
@@ -27,15 +27,16 @@ const trxId = `{
 }`
 
 var expectedTransfer = blockatlas.Tx{
-	ID:     "0x702edd54bd4e13e0012798cc8b2dfa52f7150173945103d203fae26b8e3d2ed7",
-	Coin:   coin.VET,
-	From:   "0xB5e883349e68aB59307d1604555AC890fAC47128",
-	To:     "0x2c7A8d5ccE0d5E6a8a31233B7Dc3DAE9AaE4b405",
-	Date:   1574410670,
-	Type:   blockatlas.TxTransfer,
-	Fee:    blockatlas.Amount("21000"),
-	Status: blockatlas.StatusCompleted,
-	Block:  4395940,
+	ID:        "0x702edd54bd4e13e0012798cc8b2dfa52f7150173945103d203fae26b8e3d2ed7",
+	Coin:      coin.VET,
+	From:      "0xB5e883349e68aB59307d1604555AC890fAC47128",
+	To:        "0x2c7A8d5ccE0d5E6a8a31233B7Dc3DAE9AaE4b405",
+	Date:      1574410670,
+	Type:      blockatlas.TxTransfer,
+	Fee:       blockatlas.Amount("21000"),
+	Status:    blockatlas.StatusCompleted,
+	Block:     4395940,
+	Direction: blockatlas.DirectionOutgoing,
 	Meta: blockatlas.Transfer{
 		Value:    blockatlas.Amount("1347000000000000000"),
 		Decimals: 18,
@@ -46,11 +47,12 @@ var expectedTransfer = blockatlas.Tx{
 func TestNormalizeTransaction(t *testing.T) {
 	tests := []struct {
 		name     string
+		addr     string
 		txData   string
 		txId     string
 		expected blockatlas.Tx
 	}{
-		{"Test normalize VET transfer transaction", transferSrc, trxId, expectedTransfer},
+		{"Test normalize VET transfer transaction", "0xb5e883349e68ab59307d1604555ac890fac47128", transferSrc, trxId, expectedTransfer},
 	}
 
 	platform := Platform{}
@@ -65,7 +67,7 @@ func TestNormalizeTransaction(t *testing.T) {
 			errTrxID := json.Unmarshal([]byte(tt.txId), &tId)
 			assert.Nil(t, errTrxID)
 
-			actual, err := platform.NormalizeTransaction(tx, tId)
+			actual, err := platform.NormalizeTransaction(tx, tId, tt.addr)
 			assert.Nil(t, err)
 
 			assert.Equal(t, tt.expected, actual, "tx don't equal")
@@ -133,15 +135,16 @@ const trxReceipt = `{
 
 var expectedTransferLog = blockatlas.TxPage{
 	{
-		ID:     "0x42f5eba46ddcc458243c753545a3faa849502d078efbc5b74baddea9e6ea5b04",
-		Coin:   coin.VET,
-		From:   "0x2c7A8d5ccE0d5E6a8a31233B7Dc3DAE9AaE4b405",
-		To:     "0x0000000000000000000000000000456E65726779",
-		Date:   1574278180,
-		Type:   blockatlas.TxTokenTransfer,
-		Fee:    blockatlas.Amount("36582000000000000000"),
-		Status: blockatlas.StatusCompleted,
-		Block:  4382764,
+		ID:        "0x42f5eba46ddcc458243c753545a3faa849502d078efbc5b74baddea9e6ea5b04",
+		Coin:      coin.VET,
+		From:      "0x2c7A8d5ccE0d5E6a8a31233B7Dc3DAE9AaE4b405",
+		To:        "0x0000000000000000000000000000456E65726779",
+		Date:      1574278180,
+		Type:      blockatlas.TxTokenTransfer,
+		Fee:       blockatlas.Amount("36582000000000000000"),
+		Status:    blockatlas.StatusCompleted,
+		Block:     4382764,
+		Direction: blockatlas.DirectionIncoming,
 		Meta: blockatlas.TokenTransfer{
 			Name:     gasTokenName,
 			Symbol:   gasTokenSymbol,
@@ -210,9 +213,6 @@ func Test_hexToInt(t *testing.T) {
 }
 
 func Test_getRecipientAddress(t *testing.T) {
-	type args struct {
-		hex string
-	}
 	tests := []struct {
 		name string
 		hex  string
@@ -229,3 +229,59 @@ func Test_getRecipientAddress(t *testing.T) {
 		})
 	}
 }
+
+func Test_getTokenTransactionDirectory(t *testing.T) {
+	addr1 := "0xb5e883349e68ab59307d1604555ac890fac47128"
+	addr2 := "0eec2bbedbb8b18357dab0b753cd1893bb832284"
+	tests := []struct {
+		name         string
+		originSender string
+		topicsFrom   string
+		topicsTo     string
+		expected     blockatlas.Direction
+		expectErr    bool
+	}{
+		{"Self direction", addr1, addr1, addr1, blockatlas.DirectionSelf, false},
+		{"In direction", addr1, addr1, addr2, blockatlas.DirectionIncoming, false},
+		{"Out direction", addr1, addr2, addr1, blockatlas.DirectionOutgoing, false},
+		{"Unknown direction", addr1, addr2, addr2, "", true},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			actual, err := getTokenTransactionDirectory(tt.originSender, tt.topicsFrom, tt.topicsTo)
+			if tt.expectErr {
+				assert.NotNil(t, err)
+			}
+			assert.Equal(t, tt.expected, actual)
+		})
+	}
+}
+
+func Test_getTransferDirectory(t *testing.T) {
+	addr1 := "0xb5e883349e68ab59307d1604555ac890fac47128"
+	addr2 := "0eec2bbedbb8b18357dab0b753cd1893bb832284"
+	tests := []struct {
+		name      string
+		sender    string
+		recipient string
+		address   string
+		expected  blockatlas.Direction
+		expectErr bool
+	}{
+		{"Self direction for addr1", addr1, addr1, addr1, blockatlas.DirectionSelf, false},
+		{"Self direction for addr2", addr2, addr2, addr2, blockatlas.DirectionSelf, false},
+		{"Out direction", addr1, addr2, addr1, blockatlas.DirectionOutgoing, false},
+		{"In direction", addr1, addr2, addr2, blockatlas.DirectionIncoming, false},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			actual, err := getTransferDirectory(tt.sender, tt.recipient, tt.address)
+			if tt.expectErr {
+				assert.NotNil(t, err)
+			}
+			assert.Equal(t, tt.expected, actual)
+		})
+	}
+}