Skip to content

Commit

Permalink
services/horizon: Add account_credited/debited effects for SAC ev…
Browse files Browse the repository at this point in the history
…ents. (#4806)

* Harden amount parser
* Propogate network passphrase and add tests
* Integration tests check effect values, passing 🙏
* Move stringification to xdr package
* Separate to/from addresses in contract events
  • Loading branch information
Shaptic authored Mar 21, 2023
1 parent 0fbbda4 commit 107d5d1
Show file tree
Hide file tree
Showing 11 changed files with 610 additions and 94 deletions.
16 changes: 16 additions & 0 deletions amount/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,22 @@ func String(v xdr.Int64) string {
return StringFromInt64(int64(v))
}

// String128 converts a signed 128-bit integer into a string, boldly assuming
// 7-decimal precision.
//
// TODO: This should be adapted to variable precision when appopriate, but 7
// decimals is the correct default for Stellar Classic amounts.
func String128(v xdr.Int128Parts) string {
// the upper half of the i128 always indicates its sign regardless of its
// value, just like a native signed type
val := big.NewInt(int64(v.Hi))
val.Lsh(val, 64).Add(val, new(big.Int).SetUint64(uint64(v.Lo)))

rat := new(big.Rat).SetInt(val)
rat.Quo(rat, bigOne)
return rat.FloatString(7)
}

// StringFromInt64 returns an "amount string" from the provided raw int64 value `v`.
func StringFromInt64(v int64) string {
r := big.NewRat(v, 1)
Expand Down
15 changes: 12 additions & 3 deletions services/horizon/internal/db2/history/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,20 @@ const (
// EffectAccountRemoved effects occur when one account is merged into another
EffectAccountRemoved EffectType = 1 // from merge_account

// EffectAccountCredited effects occur when an account receives some currency
EffectAccountCredited EffectType = 2 // from create_account, payment, path_payment, merge_account
// EffectAccountCredited effects occur when an account receives some
// currency
//
// from create_account, payment, path_payment, merge_account, and SAC events
// involving transfers, mints, and burns.
EffectAccountCredited EffectType = 2

// EffectAccountDebited effects occur when an account sends some currency
EffectAccountDebited EffectType = 3 // from create_account, payment, path_payment, create_account
//
// from create_account, payment, path_payment, create_account, and SAC
// involving transfers, mints, and burns.
//
// https://github.com/stellar/rs-soroban-env/blob/5695440da452837555d8f7f259cc33341fdf07b0/soroban-env-host/src/native_contract/token/contract.rs#L51-L63
EffectAccountDebited EffectType = 3

// EffectAccountThresholdsUpdated effects occur when an account changes its
// multisig thresholds.
Expand Down
2 changes: 1 addition & 1 deletion services/horizon/internal/ingest/processor_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ func (s *ProcessorRunner) buildTransactionProcessor(
sequence := uint32(ledger.Header.LedgerSeq)
return newGroupTransactionProcessors([]horizonTransactionProcessor{
statsLedgerTransactionProcessor,
processors.NewEffectProcessor(s.historyQ, sequence),
processors.NewEffectProcessor(s.historyQ, sequence, s.config.NetworkPassphrase),
processors.NewLedgerProcessor(s.historyQ, ledger, CurrentVersion),
processors.NewOperationProcessor(s.historyQ, sequence),
tradeProcessor,
Expand Down
129 changes: 120 additions & 9 deletions services/horizon/internal/ingest/processors/effects_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import (
"github.com/stellar/go/keypair"
"github.com/stellar/go/protocols/horizon/base"
"github.com/stellar/go/services/horizon/internal/db2/history"
"github.com/stellar/go/strkey"
"github.com/stellar/go/support/contractevents"
"github.com/stellar/go/support/errors"
"github.com/stellar/go/xdr"
)
Expand All @@ -24,12 +26,14 @@ type EffectProcessor struct {
effects []effect
effectsQ history.QEffects
sequence uint32
network string
}

func NewEffectProcessor(effectsQ history.QEffects, sequence uint32) *EffectProcessor {
func NewEffectProcessor(effectsQ history.QEffects, sequence uint32, networkPassphrase string) *EffectProcessor {
return &EffectProcessor{
effectsQ: effectsQ,
sequence: sequence,
network: networkPassphrase,
}
}

Expand All @@ -56,7 +60,10 @@ func (p *EffectProcessor) loadAccountIDs(ctx context.Context, accountSet map[str
return nil
}

func operationsEffects(transaction ingest.LedgerTransaction, sequence uint32) ([]effect, error) {
func operationsEffects(
transaction ingest.LedgerTransaction,
sequence uint32,
networkPassphrase string) ([]effect, error) {
effects := []effect{}

for opi, op := range transaction.Envelope.Operations() {
Expand All @@ -65,6 +72,7 @@ func operationsEffects(transaction ingest.LedgerTransaction, sequence uint32) ([
transaction: transaction,
operation: op,
ledgerSequence: sequence,
network: networkPassphrase,
}

p, err := operation.effects()
Expand Down Expand Up @@ -119,7 +127,7 @@ func (p *EffectProcessor) ProcessTransaction(ctx context.Context, transaction in
}

var effectsForTx []effect
effectsForTx, err = operationsEffects(transaction, p.sequence)
effectsForTx, err = operationsEffects(transaction, p.sequence, p.network)
if err != nil {
return err
}
Expand Down Expand Up @@ -210,8 +218,11 @@ func (operation *transactionOperationWrapper) effects() ([]effect, error) {
err = wrapper.addCreateClaimableBalanceEffects(changes)
case xdr.OperationTypeClaimClaimableBalance:
err = wrapper.addClaimClaimableBalanceEffects(changes)
case xdr.OperationTypeBeginSponsoringFutureReserves, xdr.OperationTypeEndSponsoringFutureReserves, xdr.OperationTypeRevokeSponsorship:
// The effects of these operations are obtained indirectly from the ledger entries
case xdr.OperationTypeBeginSponsoringFutureReserves,
xdr.OperationTypeEndSponsoringFutureReserves,
xdr.OperationTypeRevokeSponsorship:
// The effects of these operations are obtained indirectly from the
// ledger entries
case xdr.OperationTypeClawback:
err = wrapper.addClawbackEffects()
case xdr.OperationTypeClawbackClaimableBalance:
Expand All @@ -223,10 +234,19 @@ func (operation *transactionOperationWrapper) effects() ([]effect, error) {
case xdr.OperationTypeLiquidityPoolWithdraw:
err = wrapper.addLiquidityPoolWithdrawEffect()
case xdr.OperationTypeInvokeHostFunction:
// TODO: https://github.com/stellar/go/issues/4585
return nil, nil
// If there's an invokeHostFunction operation, there's definitely V3
// meta in the transaction, which means this error is real.
events, innerErr := operation.transaction.GetOperationEvents(operation.index)
if innerErr != nil {
return nil, innerErr
}

// For now, the only effects are related to the events themselves.
// Possible add'l work: https://github.com/stellar/go/issues/4585
err = wrapper.addInvokeHostFunctionEffects(events)

default:
return nil, fmt.Errorf("Unknown operation type: %s", op.Body.Type)
return nil, fmt.Errorf("unknown operation type: %s", op.Body.Type)
}
if err != nil {
return nil, err
Expand All @@ -246,7 +266,8 @@ func (operation *transactionOperationWrapper) effects() ([]effect, error) {

// Liquidity pools
for _, change := range changes {
// Effects caused by ChangeTrust (creation), AllowTrust and SetTrustlineFlags (removal through revocation)
// Effects caused by ChangeTrust (creation), AllowTrust and
// SetTrustlineFlags (removal through revocation)
wrapper.addLedgerEntryLiquidityPoolEffects(change)
}

Expand Down Expand Up @@ -1386,3 +1407,93 @@ func (e *effectsWrapper) addLiquidityPoolWithdrawEffect() error {
e.addMuxed(e.operation.SourceAccount(), history.EffectLiquidityPoolWithdrew, details)
return nil
}

// addInvokeHostFunctionEffects iterates through the events and generates
// account_credited and account_debited effects when it sees events related to
// the Stellar Asset Contract corresponding to those effects.
func (e *effectsWrapper) addInvokeHostFunctionEffects(events []contractevents.Event) error {
if e.operation.network == "" {
return errors.New("invokeHostFunction effects cannot be determined unless network passphrase is set")
}

for _, event := range events {
evt, err := contractevents.NewStellarAssetContractEvent(&event, e.operation.network)
if err != nil {
continue // irrelevant or unsupported event
}

details := make(map[string]interface{}, 4)
addAssetDetails(details, evt.GetAsset(), "")

//
// Note: We ignore effects that involve contracts (until the day we have
// contract_debited/credited effects, may it never come :pray:)
//

switch evt.GetType() {
// Transfer events generate an `account_debited` effect for the `from`
// (sender) and an `account_credited` effect for the `to` (recipient).
case contractevents.EventTypeTransfer:
xferEvent := evt.(*contractevents.TransferEvent)
details["amount"] = amount.String128(xferEvent.Amount)
if strkey.IsValidEd25519PublicKey(xferEvent.From) {
e.add(
xferEvent.From,
null.String{},
history.EffectAccountDebited,
details,
)
}
if strkey.IsValidEd25519PublicKey(xferEvent.To) {
e.add(
xferEvent.To,
null.String{},
history.EffectAccountCredited,
details,
)
}

// Mint events imply a non-native asset, and it results in a credit to
// the `to` recipient.
case contractevents.EventTypeMint:
mintEvent := evt.(*contractevents.MintEvent)
details["amount"] = amount.String128(mintEvent.Amount)
if strkey.IsValidEd25519PublicKey(mintEvent.To) {
e.add(
mintEvent.To,
null.String{},
history.EffectAccountCredited,
details,
)
}

// Clawback events result in a debit to the `from` address, but acts
// like a burn to the recipient, so these are functionally equivalent
case contractevents.EventTypeClawback:
cbEvent := evt.(*contractevents.ClawbackEvent)
details["amount"] = amount.String128(cbEvent.Amount)
if strkey.IsValidEd25519PublicKey(cbEvent.From) {
e.add(
cbEvent.From,
null.String{},
history.EffectAccountDebited,
details,
)
}

case contractevents.EventTypeBurn:
burnEvent := evt.(*contractevents.BurnEvent)
details["amount"] = amount.String128(burnEvent.Amount)
if strkey.IsValidEd25519PublicKey(burnEvent.From) {
e.add(
burnEvent.From,
null.String{},
history.EffectAccountDebited,
details,
)
}
}
}

return nil
}
Loading

0 comments on commit 107d5d1

Please sign in to comment.