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 }