github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/stellar/stellarsvc/stellard_copy_test.go (about)

     1  package stellarsvc
     2  
     3  // Stuff copied from stellard for mocks
     4  
     5  import (
     6  	"fmt"
     7  	"strings"
     8  
     9  	"github.com/keybase/client/go/protocol/stellar1"
    10  	"github.com/keybase/stellarnet"
    11  	"github.com/stellar/go/xdr"
    12  )
    13  
    14  type ExtractedPayment struct {
    15  	Tx         xdr.Transaction
    16  	OpType     xdr.OperationType
    17  	From       stellar1.AccountID
    18  	To         stellar1.AccountID
    19  	AmountXdr  xdr.Int64
    20  	Amount     string
    21  	Asset      stellar1.Asset
    22  	TimeBounds *xdr.TimeBounds
    23  }
    24  
    25  // Extract the balance transfer from a transaction.
    26  // Errors out if not all conditions are met:
    27  // - Tx has one operation
    28  // - The operation is one of the types [payment, create_account]
    29  // - The per-operation source account override is not set
    30  func extractPaymentTx(tx xdr.Transaction) (res ExtractedPayment, err error) {
    31  	res.Tx = tx
    32  	if len(tx.Operations) == 0 {
    33  		return res, fmt.Errorf("transaction had no operations")
    34  	}
    35  	if len(tx.Operations) != 1 {
    36  		return res, fmt.Errorf("transaction must contain only 1 operation but had %v", len(tx.Operations))
    37  	}
    38  	if tx.Operations[0].SourceAccount != nil {
    39  		return res, fmt.Errorf("transaction operation must not override source account")
    40  	}
    41  	res.From = stellar1.AccountID(tx.SourceAccount.Address())
    42  	op := tx.Operations[0].Body
    43  	res.OpType = op.Type
    44  	res.TimeBounds = tx.TimeBounds
    45  	if op, ok := op.GetPaymentOp(); ok {
    46  		res.To = stellar1.AccountID(op.Destination.Address())
    47  		res.AmountXdr = op.Amount
    48  		res.Amount, res.Asset, err = balanceXdrToProto(op.Amount, op.Asset)
    49  		if err != nil {
    50  			return res, err
    51  		}
    52  		return res, nil
    53  	}
    54  	if op, ok := op.GetCreateAccountOp(); ok {
    55  		res.To = stellar1.AccountID(op.Destination.Address())
    56  		res.AmountXdr = op.StartingBalance
    57  		res.Amount = stellarnet.StringFromStellarXdrAmount(op.StartingBalance)
    58  		res.Asset = stellar1.AssetNative()
    59  		return res, nil
    60  	}
    61  	return res, fmt.Errorf("unexpected op type: %v", op.Type)
    62  }
    63  
    64  type ExtractedRelocate struct {
    65  	Tx xdr.Transaction
    66  	// Source can be any account. They pay the fees.
    67  	Source stellar1.AccountID
    68  	From   stellar1.AccountID
    69  	To     stellar1.AccountID
    70  }
    71  
    72  // Extract a transaction that transfers all balance into another account.
    73  // Errors out if not all conditions are met:
    74  // Case 1: account_merge operation
    75  // - Tx has one operation
    76  // - The operation is of type account_merge
    77  // Case 2: Two ops: [create_account, account_merge]
    78  // - Tx has two operations operation
    79  // - ops[0] is of type create_account for 1 XLM
    80  // - ops[1] is of type account_merge to the same destination address
    81  func extractRelocateTx(tx xdr.Transaction) (res ExtractedRelocate, err error) {
    82  	res.Tx = tx
    83  	if len(tx.Operations) == 0 {
    84  		return res, fmt.Errorf("transaction had no operations")
    85  	}
    86  	if len(tx.Operations) > 2 {
    87  		return res, fmt.Errorf("transaction must contain <=2 operations but had %v", len(tx.Operations))
    88  	}
    89  	res.Source = stellar1.AccountID(tx.SourceAccount.Address())
    90  	res.From = stellar1.AccountID(tx.SourceAccount.Address())
    91  	opFinal := tx.Operations[len(tx.Operations)-1]
    92  	if opFinal.SourceAccount != nil {
    93  		res.From = stellar1.AccountID(opFinal.SourceAccount.Address())
    94  	}
    95  	destination, ok := opFinal.Body.GetDestination()
    96  	if !ok {
    97  		return res, fmt.Errorf("unexpected final operation type: %v", opFinal.Body.Type)
    98  	}
    99  	res.To = stellar1.AccountID(destination.Address())
   100  	if len(tx.Operations) == 1 {
   101  		// Return case 1
   102  		return res, nil
   103  	}
   104  	opFirst := tx.Operations[0]
   105  	if opFirst.SourceAccount != nil && !stellar1.AccountID(opFirst.SourceAccount.Address()).Eq(res.From) {
   106  		return res, fmt.Errorf("unexpected mismatch in operations' from fields: %v != %v",
   107  			stellar1.AccountID(opFirst.SourceAccount.Address()), res.From)
   108  	}
   109  	createAccount, ok := opFirst.Body.GetCreateAccountOp()
   110  	if !ok {
   111  		return res, fmt.Errorf("unexpected first operation type: %v", opFirst.Body.Type)
   112  	}
   113  	if !stellar1.AccountID(createAccount.Destination.Address()).Eq(res.To) {
   114  		return res, fmt.Errorf("unexpected mismatch in operations' destination fields: %v != %v",
   115  			res.To, stellar1.AccountID(createAccount.Destination.Address()))
   116  	}
   117  	if createAccount.StartingBalance != xdr.Int64(10000000) {
   118  		return res, fmt.Errorf("unexpected relocation amount: %v != 1 XLM", stellarnet.StringFromStellarXdrAmount(createAccount.StartingBalance))
   119  	}
   120  	// Return case 2
   121  	return res, nil
   122  }
   123  
   124  func balanceXdrToProto(amountXdr xdr.Int64, assetXdr xdr.Asset) (amount string, asset stellar1.Asset, err error) {
   125  	unpad := func(in []byte) (out string) {
   126  		return strings.TrimRight(string(in), string([]byte{0}))
   127  	}
   128  	amount = stellarnet.StringFromStellarXdrAmount(amountXdr)
   129  	switch assetXdr.Type {
   130  	case xdr.AssetTypeAssetTypeNative:
   131  		return amount, stellar1.AssetNative(), nil
   132  	case xdr.AssetTypeAssetTypeCreditAlphanum4:
   133  		if assetXdr.AlphaNum4 == nil {
   134  			return amount, asset, fmt.Errorf("balance missing alphanum4")
   135  		}
   136  		return amount, stellar1.Asset{
   137  			Type:   "credit_alphanum4",
   138  			Code:   unpad(assetXdr.AlphaNum4.AssetCode[:]),
   139  			Issuer: assetXdr.AlphaNum4.Issuer.Address(),
   140  		}, nil
   141  	case xdr.AssetTypeAssetTypeCreditAlphanum12:
   142  		if assetXdr.AlphaNum12 == nil {
   143  			return amount, asset, fmt.Errorf("balance missing alphanum12")
   144  		}
   145  		return amount, stellar1.Asset{
   146  			Type:   "credit_alphanum12",
   147  			Code:   unpad(assetXdr.AlphaNum12.AssetCode[:]),
   148  			Issuer: assetXdr.AlphaNum12.Issuer.Address(),
   149  		}, nil
   150  	default:
   151  		return amount, asset, fmt.Errorf("unsupported asset type: %v", assetXdr.Type)
   152  	}
   153  }