|
| 1 | +package ledger1 |
| 2 | + |
| 3 | +import ( |
| 4 | + "fmt" |
| 5 | + "os" |
| 6 | + "strings" |
| 7 | + |
| 8 | + "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/base" |
| 9 | + "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/colors" |
| 10 | + "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/ledger10" |
| 11 | + "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/logger" |
| 12 | + "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/normalize" |
| 13 | + "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/rpc" |
| 14 | + "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/topics" |
| 15 | + "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/types" |
| 16 | +) |
| 17 | + |
| 18 | +func (r *Reconciler1) GetStatements1(pos *types.AppPosition, trans *types.Transaction) ([]types.Statement, error) { |
| 19 | + results := make([]types.Statement, 0, 20) |
| 20 | + if ledger10.AssetOfInterest(r.opts.AssetFilters, base.FAKE_ETH_ADDRESS) { |
| 21 | + s := types.Statement{ |
| 22 | + AccountedFor: r.opts.AccountFor, |
| 23 | + Sender: trans.From, |
| 24 | + Recipient: trans.To, |
| 25 | + BlockNumber: trans.BlockNumber, |
| 26 | + TransactionIndex: trans.TransactionIndex, |
| 27 | + TransactionHash: trans.Hash, |
| 28 | + LogIndex: 0, |
| 29 | + Timestamp: trans.Timestamp, |
| 30 | + Asset: base.FAKE_ETH_ADDRESS, |
| 31 | + Symbol: "WEI", |
| 32 | + Decimals: 18, |
| 33 | + SpotPrice: 0.0, |
| 34 | + PriceSource: "not-priced", |
| 35 | + } |
| 36 | + if r.opts.AsEther { |
| 37 | + s.Symbol = "ETH" |
| 38 | + } |
| 39 | + if trans.To.IsZero() && trans.Receipt != nil && !trans.Receipt.ContractAddress.IsZero() { |
| 40 | + s.Recipient = trans.Receipt.ContractAddress |
| 41 | + } |
| 42 | + |
| 43 | + reconciled := false |
| 44 | + if !r.opts.UseTraces { |
| 45 | + if s.Sender == r.opts.AccountFor { |
| 46 | + gasUsed := new(base.Wei) |
| 47 | + if trans.Receipt != nil { |
| 48 | + gasUsed.SetUint64(uint64(trans.Receipt.GasUsed)) |
| 49 | + } |
| 50 | + gasPrice := new(base.Wei).SetUint64(uint64(trans.GasPrice)) |
| 51 | + gasOut := new(base.Wei).Mul(gasUsed, gasPrice) |
| 52 | + s.AmountOut = trans.Value |
| 53 | + s.GasOut = *gasOut |
| 54 | + } |
| 55 | + |
| 56 | + if s.Recipient == r.opts.AccountFor { |
| 57 | + if s.BlockNumber == 0 { |
| 58 | + s.PrefundIn = trans.Value |
| 59 | + } else { |
| 60 | + if trans.Rewards != nil { |
| 61 | + s.MinerBaseRewardIn = trans.Rewards.Block |
| 62 | + s.MinerNephewRewardIn = trans.Rewards.Nephew |
| 63 | + s.MinerTxFeeIn = trans.Rewards.TxFee |
| 64 | + s.MinerUncleRewardIn = trans.Rewards.Uncle |
| 65 | + } else { |
| 66 | + s.AmountIn = trans.Value |
| 67 | + } |
| 68 | + } |
| 69 | + } |
| 70 | + |
| 71 | + var err error |
| 72 | + if s.PrevBal, s.BegBal, s.EndBal, err = r.opts.Connection.GetReconBalances(&rpc.BalanceOptions{ |
| 73 | + PrevAppBlk: pos.Prev, |
| 74 | + CurrBlk: trans.BlockNumber, |
| 75 | + Asset: s.Asset, |
| 76 | + Holder: s.AccountedFor, |
| 77 | + }); err != nil { |
| 78 | + return nil, err |
| 79 | + } |
| 80 | + reconciled = r.trialBalance(pos, trans, &s) |
| 81 | + if reconciled && s.IsMaterial() { |
| 82 | + results = append(results, s) |
| 83 | + } |
| 84 | + } |
| 85 | + |
| 86 | + if r.opts.UseTraces || !reconciled { |
| 87 | + results = make([]types.Statement, 0, 20) /* reset this */ |
| 88 | + if s, err := r.getStatementFromTraces(pos, trans, &s); err != nil { |
| 89 | + logger.Warn(colors.Yellow+"Statement at ", fmt.Sprintf("%d.%d", trans.BlockNumber, trans.TransactionIndex), " does not reconcile."+colors.Off) |
| 90 | + } else { |
| 91 | + var err error |
| 92 | + if s.PrevBal, s.BegBal, s.EndBal, err = r.opts.Connection.GetReconBalances(&rpc.BalanceOptions{ |
| 93 | + PrevAppBlk: pos.Prev, |
| 94 | + CurrBlk: trans.BlockNumber, |
| 95 | + Asset: s.Asset, |
| 96 | + Holder: s.AccountedFor, |
| 97 | + }); err != nil { |
| 98 | + return nil, err |
| 99 | + } |
| 100 | + _ = r.trialBalance(pos, trans, s) |
| 101 | + results = append(results, *s) |
| 102 | + } |
| 103 | + } |
| 104 | + } |
| 105 | + |
| 106 | + if trans.Receipt != nil { |
| 107 | + if statements, err := r.getStatementsFromLogs(trans.Receipt.Logs); err != nil { |
| 108 | + return nil, err |
| 109 | + } else { |
| 110 | + receiptStatements := make([]types.Statement, 0, len(statements)) |
| 111 | + for _, s := range statements { |
| 112 | + var err error |
| 113 | + if s.PrevBal, s.BegBal, s.EndBal, err = r.opts.Connection.GetReconBalances(&rpc.BalanceOptions{ |
| 114 | + PrevAppBlk: pos.Prev, |
| 115 | + CurrBlk: trans.BlockNumber, |
| 116 | + Asset: s.Asset, |
| 117 | + Holder: s.AccountedFor, |
| 118 | + }); err != nil { |
| 119 | + return nil, err |
| 120 | + } |
| 121 | + reconciled := r.trialBalance(pos, trans, &s) |
| 122 | + |
| 123 | + if reconciled { |
| 124 | + id := fmt.Sprintf(" %d.%d.%d", s.BlockNumber, s.TransactionIndex, s.LogIndex) |
| 125 | + logger.Progress(true, colors.Green+"Transaction", id, "reconciled "+colors.Off) |
| 126 | + } else { |
| 127 | + if os.Getenv("TEST_MODE") != "true" { |
| 128 | + id := fmt.Sprintf(" %d.%d.%d", s.BlockNumber, s.TransactionIndex, s.LogIndex) |
| 129 | + logger.Warn("Log statement at ", id, " does not reconcile.") |
| 130 | + } |
| 131 | + } |
| 132 | + |
| 133 | + // order matters |
| 134 | + if s.IsMaterial() { |
| 135 | + receiptStatements = append(receiptStatements, s) |
| 136 | + } |
| 137 | + } |
| 138 | + results = append(results, receiptStatements...) |
| 139 | + } |
| 140 | + } |
| 141 | + |
| 142 | + return results, nil |
| 143 | +} |
| 144 | + |
| 145 | +func (r *Reconciler1) getStatementFromTraces(pos *types.AppPosition, trans *types.Transaction, s *types.Statement) (*types.Statement, error) { |
| 146 | + |
| 147 | + ret := *s |
| 148 | + // clear all the internal accounting values. Keeps AmountIn, AmountOut and GasOut because |
| 149 | + // those are at the top level (both the transaction itself and trace '0' have them). We |
| 150 | + // skip trace '0' because it's the same as the transaction. |
| 151 | + // ret.AmountIn.SetUint64(0) |
| 152 | + ret.InternalIn.SetUint64(0) |
| 153 | + ret.MinerBaseRewardIn.SetUint64(0) |
| 154 | + ret.MinerNephewRewardIn.SetUint64(0) |
| 155 | + ret.MinerTxFeeIn.SetUint64(0) |
| 156 | + ret.MinerUncleRewardIn.SetUint64(0) |
| 157 | + ret.CorrectingIn.SetUint64(0) |
| 158 | + ret.PrefundIn.SetUint64(0) |
| 159 | + ret.SelfDestructIn.SetUint64(0) |
| 160 | + |
| 161 | + // ret.AmountOut.SetUint64(0) |
| 162 | + // ret.GasOut.SetUint64(0) |
| 163 | + ret.InternalOut.SetUint64(0) |
| 164 | + ret.CorrectingOut.SetUint64(0) |
| 165 | + ret.SelfDestructOut.SetUint64(0) |
| 166 | + |
| 167 | + if traces, err := r.opts.Connection.GetTracesByTransactionHash(trans.Hash.Hex(), trans); err != nil { |
| 168 | + return nil, err |
| 169 | + |
| 170 | + } else { |
| 171 | + // These values accumulate...so we use += instead of = |
| 172 | + for i, trace := range traces { |
| 173 | + if i == 0 { |
| 174 | + // the first trace is identical to the transaction itself, so we can skip it |
| 175 | + continue |
| 176 | + } |
| 177 | + |
| 178 | + if trace.Action.CallType == "delegatecall" && trace.Action.To != s.AccountedFor { |
| 179 | + // delegate calls are not included in the transaction's gas cost, so we skip them |
| 180 | + continue |
| 181 | + } |
| 182 | + |
| 183 | + plusEq := func(a1, a2 *base.Wei) base.Wei { |
| 184 | + return *a1.Add(a1, a2) |
| 185 | + } |
| 186 | + |
| 187 | + // Do not collapse, more than one of these can be true at the same time |
| 188 | + if trace.Action.From == s.AccountedFor { |
| 189 | + ret.InternalOut = plusEq(&ret.InternalOut, &trace.Action.Value) |
| 190 | + ret.Sender = trace.Action.From |
| 191 | + if trace.Action.To.IsZero() { |
| 192 | + if trace.Result != nil { |
| 193 | + ret.Recipient = trace.Result.Address |
| 194 | + } |
| 195 | + } else { |
| 196 | + ret.Recipient = trace.Action.To |
| 197 | + } |
| 198 | + } |
| 199 | + |
| 200 | + if trace.Action.To == s.AccountedFor { |
| 201 | + ret.InternalIn = plusEq(&ret.InternalIn, &trace.Action.Value) |
| 202 | + ret.Sender = trace.Action.From |
| 203 | + ret.Recipient = trace.Action.To |
| 204 | + } |
| 205 | + |
| 206 | + if trace.Action.SelfDestructed == s.AccountedFor { |
| 207 | + ret.SelfDestructOut = plusEq(&ret.SelfDestructOut, &trace.Action.Balance) |
| 208 | + ret.Sender = trace.Action.SelfDestructed |
| 209 | + if ret.Sender.IsZero() { |
| 210 | + ret.Sender = trace.Action.Address |
| 211 | + } |
| 212 | + ret.Recipient = trace.Action.RefundAddress |
| 213 | + } |
| 214 | + |
| 215 | + if trace.Action.RefundAddress == s.AccountedFor { |
| 216 | + ret.SelfDestructIn = plusEq(&ret.SelfDestructIn, &trace.Action.Balance) |
| 217 | + ret.Sender = trace.Action.SelfDestructed |
| 218 | + if ret.Sender.IsZero() { |
| 219 | + ret.Sender = trace.Action.Address |
| 220 | + } |
| 221 | + ret.Recipient = trace.Action.RefundAddress |
| 222 | + } |
| 223 | + |
| 224 | + if trace.Action.Address == s.AccountedFor && !trace.Action.RefundAddress.IsZero() { |
| 225 | + ret.SelfDestructOut = plusEq(&ret.SelfDestructOut, &trace.Action.Balance) |
| 226 | + // self destructed send |
| 227 | + ret.Sender = trace.Action.Address |
| 228 | + ret.Recipient = trace.Action.RefundAddress |
| 229 | + } |
| 230 | + |
| 231 | + if trace.Result != nil { |
| 232 | + if trace.Result.Address == s.AccountedFor { |
| 233 | + ret.InternalIn = plusEq(&ret.InternalIn, &trace.Action.Value) |
| 234 | + ret.Sender = trace.Action.From |
| 235 | + ret.Recipient = trace.Result.Address |
| 236 | + } |
| 237 | + } |
| 238 | + } |
| 239 | + } |
| 240 | + |
| 241 | + return &ret, nil |
| 242 | +} |
| 243 | + |
| 244 | +func (r *Reconciler1) getStatementsFromLogs(logs []types.Log) ([]types.Statement, error) { |
| 245 | + receiptStatements := make([]types.Statement, 0, 20) |
| 246 | + for _, log := range logs { |
| 247 | + if log.Topics[0] != topics.TransferTopic { |
| 248 | + continue |
| 249 | + } |
| 250 | + addrArray := []base.Address{r.opts.AccountFor} |
| 251 | + if r.opts.AppFilters.ApplyLogFilter(&log, addrArray) && ledger10.AssetOfInterest(r.opts.AssetFilters, log.Address) { |
| 252 | + normalized, err := normalize.NormalizeKnownLogs(&log) |
| 253 | + if err != nil { |
| 254 | + continue |
| 255 | + } else if normalized.IsNFT() { |
| 256 | + continue |
| 257 | + } else { |
| 258 | + sender := base.HexToAddress(normalized.Topics[1].Hex()) |
| 259 | + recipient := base.HexToAddress(normalized.Topics[2].Hex()) |
| 260 | + isSender, isRecipient := r.opts.AccountFor == sender, r.opts.AccountFor == recipient |
| 261 | + if !isSender && !isRecipient { |
| 262 | + continue |
| 263 | + } |
| 264 | + |
| 265 | + sym := normalized.Address.DefaultSymbol() |
| 266 | + decimals := base.Value(18) |
| 267 | + name := r.names[normalized.Address] |
| 268 | + if name.Address == normalized.Address { |
| 269 | + if name.Symbol != "" { |
| 270 | + sym = name.Symbol |
| 271 | + } |
| 272 | + if name.Decimals != 0 { |
| 273 | + decimals = base.Value(name.Decimals) |
| 274 | + } |
| 275 | + } |
| 276 | + |
| 277 | + var amountIn, amountOut base.Wei |
| 278 | + amount, _ := new(base.Wei).SetString(strings.ReplaceAll(normalized.Data, "0x", ""), 16) |
| 279 | + if amount == nil { |
| 280 | + amount = base.NewWei(0) |
| 281 | + } |
| 282 | + if r.opts.AccountFor == sender { |
| 283 | + amountOut = *amount |
| 284 | + } |
| 285 | + if r.opts.AccountFor == recipient { |
| 286 | + amountIn = *amount |
| 287 | + } |
| 288 | + s := types.Statement{ |
| 289 | + AccountedFor: r.opts.AccountFor, |
| 290 | + Sender: sender, |
| 291 | + Recipient: recipient, |
| 292 | + BlockNumber: normalized.BlockNumber, |
| 293 | + TransactionIndex: normalized.TransactionIndex, |
| 294 | + LogIndex: normalized.LogIndex, |
| 295 | + TransactionHash: normalized.TransactionHash, |
| 296 | + Timestamp: normalized.Timestamp, |
| 297 | + Asset: normalized.Address, |
| 298 | + Symbol: sym, |
| 299 | + Decimals: decimals, |
| 300 | + SpotPrice: 0.0, |
| 301 | + PriceSource: "not-priced", |
| 302 | + AmountIn: amountIn, |
| 303 | + AmountOut: amountOut, |
| 304 | + } |
| 305 | + receiptStatements = append(receiptStatements, s) |
| 306 | + } |
| 307 | + } |
| 308 | + } |
| 309 | + return receiptStatements, nil |
| 310 | +} |
0 commit comments