github.com/TrueBlocks/trueblocks-core/src/apps/chifra@v0.0.0-20241022031540-b362680128f7/pkg/ledger/stmnt_from_log.go (about)

     1  package ledger
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/base"
     8  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/colors"
     9  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/logger"
    10  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/rpc"
    11  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/types"
    12  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/utils"
    13  )
    14  
    15  var transferTopic = base.HexToHash(
    16  	"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
    17  )
    18  
    19  var ErrNonIndexedTransfer = fmt.Errorf("non-indexed transfer")
    20  
    21  // getStatementsFromLog returns a statement from a given log
    22  func (l *Ledger) getStatementsFromLog(conn *rpc.Connection, logIn *types.Log) (types.Statement, error) {
    23  	if logIn.Topics[0] != transferTopic {
    24  		// Not a transfer
    25  		return types.Statement{}, nil
    26  	}
    27  
    28  	if log, err := l.normalizeTransfer(logIn); err != nil {
    29  		return types.Statement{}, err
    30  
    31  	} else {
    32  		sym := log.Address.Prefix(6)
    33  		decimals := base.Value(18)
    34  		name := l.Names[log.Address]
    35  		if name.Address == log.Address {
    36  			if name.Symbol != "" {
    37  				sym = name.Symbol
    38  			}
    39  			if name.Decimals != 0 {
    40  				decimals = base.Value(name.Decimals)
    41  			}
    42  		}
    43  
    44  		sender := base.HexToAddress(log.Topics[1].Hex())
    45  		recipient := base.HexToAddress(log.Topics[2].Hex())
    46  		var amountIn, amountOut base.Wei
    47  		var amt *base.Wei
    48  		if amt, _ = new(base.Wei).SetString(strings.Replace(log.Data, "0x", "", -1), 16); amt == nil {
    49  			amt = base.NewWei(0)
    50  		}
    51  
    52  		ofInterest := false
    53  
    54  		// Do not collapse, may be both
    55  		if l.AccountFor == sender {
    56  			amountOut = *amt
    57  			ofInterest = true
    58  		}
    59  
    60  		// Do not collapse, may be both
    61  		if l.AccountFor == recipient {
    62  			amountIn = *amt
    63  			ofInterest = true
    64  		}
    65  
    66  		s := types.Statement{
    67  			AccountedFor:     l.AccountFor,
    68  			Sender:           sender,
    69  			Recipient:        recipient,
    70  			BlockNumber:      log.BlockNumber,
    71  			TransactionIndex: log.TransactionIndex,
    72  			LogIndex:         log.LogIndex,
    73  			TransactionHash:  log.TransactionHash,
    74  			Timestamp:        log.Timestamp,
    75  			AssetAddr:        log.Address,
    76  			AssetSymbol:      sym,
    77  			Decimals:         decimals,
    78  			SpotPrice:        0.0,
    79  			PriceSource:      "not-priced",
    80  			AmountIn:         amountIn,
    81  			AmountOut:        amountOut,
    82  		}
    83  
    84  		// TODO: BOGUS PERF - WE HIT GETBALANCE THREE TIMES FOR EACH APPEARANCE. SPIN THROUGH ONCE
    85  		// TODO: AND CACHE RESULTS IN MEMORY, BUT BE CAREFUL OF MULTIPLE LOGS PER BLOCK (OR TRANSACTION)
    86  		key := l.ctxKey(log.BlockNumber, log.TransactionIndex)
    87  		ctx := l.Contexts[key]
    88  
    89  		if ofInterest {
    90  			var err error
    91  			pBal := new(base.Wei)
    92  			if pBal, err = conn.GetBalanceAtToken(log.Address, l.AccountFor, fmt.Sprintf("0x%x", ctx.PrevBlock)); pBal == nil {
    93  				return s, err
    94  			}
    95  			s.PrevBal = *pBal
    96  
    97  			bBal := new(base.Wei)
    98  			if bBal, err = conn.GetBalanceAtToken(log.Address, l.AccountFor, fmt.Sprintf("0x%x", ctx.CurBlock-1)); bBal == nil {
    99  				return s, err
   100  			}
   101  			s.BegBal = *bBal
   102  
   103  			eBal := new(base.Wei)
   104  			if eBal, err = conn.GetBalanceAtToken(log.Address, l.AccountFor, fmt.Sprintf("0x%x", ctx.CurBlock)); eBal == nil {
   105  				return s, err
   106  			}
   107  			s.EndBal = *eBal
   108  
   109  			id := fmt.Sprintf(" %d.%d.%d", s.BlockNumber, s.TransactionIndex, s.LogIndex)
   110  			if !l.trialBalance("token", &s) {
   111  				if !utils.IsFuzzing() {
   112  					logger.Warn(colors.Yellow+"Log statement at ", id, " does not reconcile."+colors.Off)
   113  				}
   114  			} else {
   115  				if !utils.IsFuzzing() {
   116  					logger.Progress(true, colors.Green+"Transaction", id, "reconciled       "+colors.Off)
   117  				}
   118  			}
   119  		}
   120  
   121  		return s, nil
   122  	}
   123  }
   124  
   125  func (l *Ledger) normalizeTransfer(log *types.Log) (*types.Log, error) {
   126  	if len(log.Topics) < 3 {
   127  		// Transfer(address _from, address _to, uint256 _tokenId) - no indexed topics
   128  		// Transfer(address indexed _from, address indexed _to, uint256 _value) - two indexed topics
   129  		// Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId) - three indexed topics
   130  		// TODO: This may be a transfer. Returning here is wrong. What this means is that
   131  		// TODO:the some of the transfer's data is not indexed. Too short topics happens
   132  		// TODO: (sometimes) because the ABI says that the data is not index, but it is
   133  		// TODO: or visa versa. In either case, we get the same topic0. We need to
   134  		// TODO: attempt both with and without indexed parameters. See issues/1366.
   135  		// TODO: We could fix this and call back in recursively...
   136  		return nil, ErrNonIndexedTransfer
   137  	}
   138  
   139  	return log, nil
   140  }