github.com/algorand/go-algorand-sdk@v1.24.0/future/atomicTransactionComposer.go (about) 1 package future 2 3 import ( 4 "bytes" 5 "context" 6 "errors" 7 "fmt" 8 9 "github.com/algorand/go-algorand-sdk/abi" 10 "github.com/algorand/go-algorand-sdk/client/v2/algod" 11 "github.com/algorand/go-algorand-sdk/client/v2/common/models" 12 "github.com/algorand/go-algorand-sdk/crypto" 13 "github.com/algorand/go-algorand-sdk/types" 14 ) 15 16 // abiReturnHash is the 4-byte prefix for logged return values, from https://github.com/algorandfoundation/ARCs/blob/main/ARCs/arc-0004.md#standard-format 17 var abiReturnHash = []byte{0x15, 0x1f, 0x7c, 0x75} 18 19 // maxAppArgs is the maximum number of arguments for an application call transaction at the time 20 // ARC-4 was created 21 const maxAppArgs = 16 22 23 // The tuple threshold is maxAppArgs, minus 1 for the method selector in the first app arg, 24 // minus 1 for the final app argument becoming a tuple of the remaining method args 25 const methodArgsTupleThreshold = maxAppArgs - 2 26 27 // TransactionWithSigner represents an unsigned transactions and a signer that can authorize that 28 // transaction. 29 type TransactionWithSigner struct { 30 // An unsigned transaction 31 Txn types.Transaction 32 // A transaction signer that can authorize the transaction 33 Signer TransactionSigner 34 } 35 36 // ABIMethodResult represents the output from a successful ABI method call. 37 type ABIMethodResult struct { 38 // The TxID of the transaction that invoked the ABI method call. 39 TxID string 40 // Information about the confirmed transaction that invoked the ABI method call. 41 TransactionInfo models.PendingTransactionInfoResponse 42 // Method that was called for this ABIMethodResult 43 Method abi.Method 44 // The raw bytes of the return value from the ABI method call. This will be empty if the method 45 // does not return a value (return type "void"). 46 RawReturnValue []byte 47 // The return value from the ABI method call. This will be nil if the method does not return 48 // a value (return type "void"), or if the SDK was unable to decode the returned value. 49 ReturnValue interface{} 50 // If the SDK was unable to decode a return value, the error will be here. Make sure to check 51 // this before examinging ReturnValue 52 DecodeError error 53 } 54 55 // AddMethodCallParams contains the parameters for the method AtomicTransactionComposer.AddMethodCall 56 type AddMethodCallParams struct { 57 // The ID of the smart contract to call. Set this to 0 to indicate an application creation call. 58 AppID uint64 59 // The method to call on the smart contract 60 Method abi.Method 61 // The arguments to include in the method call. If omitted, no arguments will be passed to the 62 // method. 63 MethodArgs []interface{} 64 // The address of the sender of this application call 65 Sender types.Address 66 // Transactions params to use for this application call 67 SuggestedParams types.SuggestedParams 68 // The OnComplete action to take for this application call 69 OnComplete types.OnCompletion 70 // The approval program for this application call. Only set this if this is an application 71 // creation call, or if onComplete is UpdateApplicationOC. 72 ApprovalProgram []byte 73 // The clear program for this application call. Only set this if this is an application creation 74 // call, or if onComplete is UpdateApplicationOC. 75 ClearProgram []byte 76 // The global schema sizes. Only set this if this is an application creation call. 77 GlobalSchema types.StateSchema 78 // The local schema sizes. Only set this if this is an application creation call. 79 LocalSchema types.StateSchema 80 // The number of extra pages to allocate for the application's programs. Only set this if this 81 // is an application creation call. 82 ExtraPages uint32 83 // The note value for this application call 84 Note []byte 85 // The lease value for this application call 86 Lease [32]byte 87 // If provided, the address that the sender will be rekeyed to at the conclusion of this application call 88 RekeyTo types.Address 89 // A transaction Signer that can authorize this application call from sender 90 Signer TransactionSigner 91 // Any foreign apps to be passed that aren't part of the method signature. 92 // If apps are provided here, the apps specified in the method args will appear after these 93 ForeignApps []uint64 94 // Any foreign assets to be passed that aren't part of the method signature 95 // If assets are provided here, the assets specified in the method args will appear after these 96 ForeignAssets []uint64 97 // Any foreign accounts to be passed that aren't part of the method signature 98 // If accounts are provided here, the accounts specified in the method args will appear after these 99 ForeignAccounts []string 100 101 // References of the boxes to be accessed by this method call. 102 BoxReferences []types.AppBoxReference 103 } 104 105 // ExecuteResult contains the results of successfully calling the Execute method on an 106 // AtomicTransactionComposer object. 107 type ExecuteResult struct { 108 // The round in which the executed transaction group was confirmed on chain 109 ConfirmedRound uint64 110 // A list of the TxIDs for each transaction in the executed group 111 TxIDs []string 112 // For each ABI method call in the executed group (created by the AddMethodCall method), this 113 // slice contains information about the method call's return value 114 MethodResults []ABIMethodResult 115 } 116 117 // AtomicTransactionComposerStatus represents the status of an AtomicTransactionComposer 118 type AtomicTransactionComposerStatus = int 119 120 const ( 121 // The atomic group is still under construction. 122 BUILDING AtomicTransactionComposerStatus = iota 123 124 // The atomic group has been finalized, but not yet signed. 125 BUILT 126 127 // The atomic group has been finalized and signed, but not yet submitted to the network. 128 SIGNED 129 130 // The atomic group has been finalized, signed, and submitted to the network. 131 SUBMITTED 132 133 // The atomic group has been finalized, signed, submitted, and successfully committed to a block. 134 COMMITTED 135 ) 136 137 type transactionContext struct { 138 // The main transaction. 139 txn types.Transaction 140 141 // The corresponding signer responsible for producing the signed transaction. 142 signer TransactionSigner 143 144 // The corresponding Method constructed from information passed into atc.AddMethodCall(). 145 method *abi.Method 146 147 // The raw signed transaction populated after invocation of atc.GatherSignatures(). 148 stxBytes []byte 149 150 // The txid of the transaction, empty until populated by a call to txContext.txID() 151 txid string 152 } 153 154 func (txContext *transactionContext) txID() string { 155 if txContext.txid == "" { 156 txContext.txid = crypto.GetTxID(txContext.txn) 157 } 158 return txContext.txid 159 } 160 161 func (txContext *transactionContext) isMethodCallTx() bool { 162 return txContext.method != nil 163 } 164 165 // The maximum size of an atomic transaction group. 166 const MaxAtomicGroupSize = 16 167 168 // AtomicTransactionComposer is a helper class used to construct and execute atomic transaction groups 169 type AtomicTransactionComposer struct { 170 // The current status of the composer. The status increases monotonically. 171 status AtomicTransactionComposerStatus 172 173 // The transaction contexts in the group with their respective signers. 174 // If status is greater than BUILDING, then this slice cannot change. 175 txContexts []transactionContext 176 } 177 178 // GetStatus returns the status of this composer's transaction group. 179 func (atc *AtomicTransactionComposer) GetStatus() AtomicTransactionComposerStatus { 180 return atc.status 181 } 182 183 // Count returns the number of transactions currently in this atomic group. 184 func (atc *AtomicTransactionComposer) Count() int { 185 return len(atc.txContexts) 186 } 187 188 // Clone creates a new composer with the same underlying transactions. The new composer's status 189 // will be BUILDING, so additional transactions may be added to it. 190 func (atc *AtomicTransactionComposer) Clone() AtomicTransactionComposer { 191 newTxContexts := make([]transactionContext, len(atc.txContexts)) 192 copy(newTxContexts, atc.txContexts) 193 for i := range newTxContexts { 194 newTxContexts[i].txn.Group = types.Digest{} 195 } 196 197 if len(newTxContexts) == 0 { 198 newTxContexts = nil 199 } 200 201 return AtomicTransactionComposer{ 202 status: BUILDING, 203 txContexts: newTxContexts, 204 } 205 } 206 207 func (atc *AtomicTransactionComposer) validateTransaction(txn types.Transaction, expectedType string) error { 208 emtpyGroup := types.Digest{} 209 if txn.Group != emtpyGroup { 210 return fmt.Errorf("expected empty group id") 211 } 212 213 if expectedType != abi.AnyTransactionType && expectedType != string(txn.Type) { 214 return fmt.Errorf("expected transaction with type %s, but got type %s", expectedType, string(txn.Type)) 215 } 216 217 return nil 218 } 219 220 // AddTransaction adds a transaction to this atomic group. 221 // 222 // An error will be thrown if the composer's status is not BUILDING, or if adding this transaction 223 // causes the current group to exceed MaxAtomicGroupSize. 224 func (atc *AtomicTransactionComposer) AddTransaction(txnAndSigner TransactionWithSigner) error { 225 if atc.status != BUILDING { 226 return errors.New("status must be BUILDING in order to add tranactions") 227 } 228 229 if atc.Count() == MaxAtomicGroupSize { 230 return fmt.Errorf("reached max group size: %d", MaxAtomicGroupSize) 231 } 232 233 err := atc.validateTransaction(txnAndSigner.Txn, abi.AnyTransactionType) 234 if err != nil { 235 return err 236 } 237 238 txContext := transactionContext{ 239 txn: txnAndSigner.Txn, 240 signer: txnAndSigner.Signer, 241 } 242 atc.txContexts = append(atc.txContexts, txContext) 243 return nil 244 } 245 246 // AddMethodCall adds a smart contract method call to this atomic group. 247 // 248 // An error will be thrown if the composer's status is not BUILDING, if adding this transaction 249 // causes the current group to exceed MaxAtomicGroupSize, or if the provided arguments are invalid 250 // for the given method. 251 func (atc *AtomicTransactionComposer) AddMethodCall(params AddMethodCallParams) error { 252 if atc.status != BUILDING { 253 return errors.New("status must be BUILDING in order to add transactions") 254 } 255 256 if len(params.MethodArgs) != len(params.Method.Args) { 257 return fmt.Errorf("the incorrect number of arguments were provided: %d != %d", len(params.MethodArgs), len(params.Method.Args)) 258 } 259 260 if atc.Count()+params.Method.GetTxCount() > MaxAtomicGroupSize { 261 return fmt.Errorf("reached max group size: %d", MaxAtomicGroupSize) 262 } 263 264 if params.AppID == 0 { 265 if len(params.ApprovalProgram) == 0 || len(params.ClearProgram) == 0 { 266 return fmt.Errorf("ApprovalProgram and ClearProgram must be provided for an application creation call") 267 } 268 } else if params.OnComplete == types.UpdateApplicationOC { 269 if len(params.ApprovalProgram) == 0 || len(params.ClearProgram) == 0 { 270 return fmt.Errorf("ApprovalProgram and ClearProgram must be provided for an application update call") 271 } 272 if (params.GlobalSchema != types.StateSchema{}) || (params.LocalSchema != types.StateSchema{}) { 273 return fmt.Errorf("GlobalSchema and LocalSchema must not be provided for an application update call") 274 } 275 } else if len(params.ApprovalProgram) != 0 || len(params.ClearProgram) != 0 || (params.GlobalSchema != types.StateSchema{}) || (params.LocalSchema != types.StateSchema{}) { 276 return fmt.Errorf("ApprovalProgram, ClearProgram, GlobalSchema, and LocalSchema must not be provided for a non-creation call") 277 } 278 279 var txsToAdd []TransactionWithSigner 280 var basicArgValues []interface{} 281 var basicArgTypes []abi.Type 282 var refArgValues []interface{} 283 var refArgTypes []string 284 refArgIndexToBasicArgIndex := make(map[int]int) 285 for i, arg := range params.Method.Args { 286 argValue := params.MethodArgs[i] 287 288 if arg.IsTransactionArg() { 289 txnAndSigner, ok := argValue.(TransactionWithSigner) 290 if !ok { 291 return fmt.Errorf("invalid arg type, expected transaction") 292 } 293 294 err := atc.validateTransaction(txnAndSigner.Txn, arg.Type) 295 if err != nil { 296 return err 297 } 298 txsToAdd = append(txsToAdd, txnAndSigner) 299 } else { 300 var abiType abi.Type 301 var err error 302 303 if arg.IsReferenceArg() { 304 refArgIndexToBasicArgIndex[len(refArgTypes)] = len(basicArgTypes) 305 refArgValues = append(refArgValues, argValue) 306 refArgTypes = append(refArgTypes, arg.Type) 307 308 // treat the reference as a uint8 for encoding purposes 309 abiType, err = abi.TypeOf("uint8") 310 } else { 311 abiType, err = arg.GetTypeObject() 312 } 313 if err != nil { 314 return err 315 } 316 317 basicArgValues = append(basicArgValues, argValue) 318 basicArgTypes = append(basicArgTypes, abiType) 319 } 320 } 321 322 // copy foreign arrays before modifying in populateMethodCallReferenceArgs 323 foreignAccounts := make([]string, len(params.ForeignAccounts)) 324 copy(foreignAccounts, params.ForeignAccounts) 325 foreignApps := make([]uint64, len(params.ForeignApps)) 326 copy(foreignApps, params.ForeignApps) 327 foreignAssets := make([]uint64, len(params.ForeignAssets)) 328 copy(foreignAssets, params.ForeignAssets) 329 330 refArgsResolved, err := populateMethodCallReferenceArgs( 331 params.Sender.String(), 332 params.AppID, 333 refArgTypes, 334 refArgValues, 335 &foreignAccounts, 336 &foreignApps, 337 &foreignAssets, 338 ) 339 if err != nil { 340 return err 341 } 342 for i, resolved := range refArgsResolved { 343 basicArgIndex := refArgIndexToBasicArgIndex[i] 344 // use the foreign array index as the encoded argument value 345 basicArgValues[basicArgIndex] = resolved 346 } 347 348 // Up to 16 app arguments can be passed to app call. First is reserved for method selector, 349 // and the rest are for method call arguments. But if more than 15 method call arguments 350 // are present, then the method arguments after the 14th are placed in a tuple in the last app 351 // argument slot 352 if len(basicArgValues) > maxAppArgs-1 { 353 typesForTuple := make([]abi.Type, len(basicArgTypes)-methodArgsTupleThreshold) 354 copy(typesForTuple, basicArgTypes[methodArgsTupleThreshold:]) 355 356 valueForTuple := make([]interface{}, len(basicArgValues)-methodArgsTupleThreshold) 357 copy(valueForTuple, basicArgValues[methodArgsTupleThreshold:]) 358 359 tupleType, err := abi.MakeTupleType(typesForTuple) 360 if err != nil { 361 return err 362 } 363 364 basicArgValues = append(basicArgValues[:methodArgsTupleThreshold], valueForTuple) 365 basicArgTypes = append(basicArgTypes[:methodArgsTupleThreshold], tupleType) 366 } 367 368 encodedAbiArgs := [][]byte{params.Method.GetSelector()} 369 370 for i, abiArg := range basicArgValues { 371 encodedArg, err := basicArgTypes[i].Encode(abiArg) 372 if err != nil { 373 return err 374 } 375 376 encodedAbiArgs = append(encodedAbiArgs, encodedArg) 377 } 378 379 tx, err := MakeApplicationCallTxWithBoxes( 380 params.AppID, 381 encodedAbiArgs, 382 foreignAccounts, 383 foreignApps, 384 foreignAssets, 385 params.BoxReferences, 386 params.OnComplete, 387 params.ApprovalProgram, 388 params.ClearProgram, 389 params.GlobalSchema, 390 params.LocalSchema, 391 params.ExtraPages, 392 params.SuggestedParams, 393 params.Sender, 394 params.Note, 395 types.Digest{}, 396 params.Lease, 397 params.RekeyTo) 398 if err != nil { 399 return err 400 } 401 402 txAndSigner := TransactionWithSigner{ 403 Txn: tx, 404 Signer: params.Signer, 405 } 406 407 for _, txAndSigner := range txsToAdd { 408 txContext := transactionContext{ 409 txn: txAndSigner.Txn, 410 signer: txAndSigner.Signer, 411 } 412 atc.txContexts = append(atc.txContexts, txContext) 413 } 414 415 methodCallTxContext := transactionContext{ 416 txn: txAndSigner.Txn, 417 signer: txAndSigner.Signer, 418 method: ¶ms.Method, 419 } 420 atc.txContexts = append(atc.txContexts, methodCallTxContext) 421 return nil 422 } 423 424 func (atc *AtomicTransactionComposer) getFinalizedTxWithSigners() []TransactionWithSigner { 425 txWithSigners := make([]TransactionWithSigner, len(atc.txContexts)) 426 for i, txContext := range atc.txContexts { 427 txWithSigners[i] = TransactionWithSigner{ 428 Txn: txContext.txn, 429 Signer: txContext.signer, 430 } 431 } 432 return txWithSigners 433 } 434 435 // BuildGroup finalizes the transaction group and returned the finalized transactions. 436 // 437 // The composer's status will be at least BUILT after executing this method. 438 func (atc *AtomicTransactionComposer) BuildGroup() ([]TransactionWithSigner, error) { 439 if atc.status > BUILDING { 440 return atc.getFinalizedTxWithSigners(), nil 441 } 442 443 if atc.Count() == 0 { 444 return nil, fmt.Errorf("attempting to build group with zero transactions") 445 } 446 447 var txns []types.Transaction 448 for _, txContext := range atc.txContexts { 449 txns = append(txns, txContext.txn) 450 } 451 452 if len(txns) > 1 { 453 gid, err := crypto.ComputeGroupID(txns) 454 if err != nil { 455 return nil, err 456 } 457 458 for i := range atc.txContexts { 459 atc.txContexts[i].txn.Group = gid 460 } 461 } 462 463 atc.status = BUILT 464 return atc.getFinalizedTxWithSigners(), nil 465 } 466 467 func (atc *AtomicTransactionComposer) getRawSignedTxs() [][]byte { 468 stxs := make([][]byte, len(atc.txContexts)) 469 for i, txContext := range atc.txContexts { 470 stxs[i] = txContext.stxBytes 471 } 472 return stxs 473 } 474 475 // GatherSignatures obtains signatures for each transaction in this group. If signatures have 476 // already been obtained, this method will return cached versions of the signatures. 477 // 478 // The composer's status will be at least SIGNED after executing this method. 479 // 480 // An error will be thrown if signing any of the transactions fails. Otherwise, this will return an 481 // array of signed transactions. 482 func (atc *AtomicTransactionComposer) GatherSignatures() ([][]byte, error) { 483 // if status is at least signed then return cached signed transactions 484 if atc.status >= SIGNED { 485 return atc.getRawSignedTxs(), nil 486 } 487 488 // retrieve built transactions and verify status is BUILT 489 txsWithSigners, err := atc.BuildGroup() 490 if err != nil { 491 return nil, err 492 } 493 494 var txs []types.Transaction 495 for _, txWithSigner := range txsWithSigners { 496 txs = append(txs, txWithSigner.Txn) 497 } 498 499 visited := make([]bool, len(txs)) 500 rawSignedTxs := make([][]byte, len(txs)) 501 for i, txWithSigner := range txsWithSigners { 502 if visited[i] { 503 continue 504 } 505 506 var indexesToSign []int 507 for j, other := range txsWithSigners { 508 if !visited[j] && txWithSigner.Signer.Equals(other.Signer) { 509 indexesToSign = append(indexesToSign, j) 510 visited[j] = true 511 } 512 } 513 514 if len(indexesToSign) == 0 { 515 return nil, fmt.Errorf("invalid tx signer provided, isn't equal to self") 516 } 517 518 sigStxs, err := txWithSigner.Signer.SignTransactions(txs, indexesToSign) 519 if err != nil { 520 return nil, err 521 } 522 523 for i, index := range indexesToSign { 524 rawSignedTxs[index] = sigStxs[i] 525 } 526 } 527 528 for i, stxBytes := range rawSignedTxs { 529 atc.txContexts[i].stxBytes = stxBytes 530 } 531 atc.status = SIGNED 532 return rawSignedTxs, nil 533 } 534 535 func (atc *AtomicTransactionComposer) getTxIDs() []string { 536 txIDs := make([]string, len(atc.txContexts)) 537 for i, txContext := range atc.txContexts { 538 txIDs[i] = txContext.txID() 539 } 540 return txIDs 541 } 542 543 // Submit sends the transaction group to the network, but doesn't wait for it to be committed to a 544 // block. An error will be thrown if submission fails. 545 // 546 // The composer's status must be SUBMITTED or lower before calling this method. If submission is 547 // successful, this composer's status will update to SUBMITTED. 548 // 549 // Note: a group can only be submitted again if it fails. 550 // 551 // Returns a list of TxIDs of the submitted transactions. 552 func (atc *AtomicTransactionComposer) Submit(client *algod.Client, ctx context.Context) ([]string, error) { 553 if atc.status > SUBMITTED { 554 return nil, errors.New("status must be SUBMITTED or lower in order to call Submit()") 555 } 556 557 stxs, err := atc.GatherSignatures() 558 if err != nil { 559 return nil, err 560 } 561 562 var serializedStxs []byte 563 for _, stx := range stxs { 564 serializedStxs = append(serializedStxs, stx...) 565 } 566 567 _, err = client.SendRawTransaction(serializedStxs).Do(ctx) 568 if err != nil { 569 return nil, err 570 } 571 572 atc.status = SUBMITTED 573 return atc.getTxIDs(), nil 574 } 575 576 // Execute sends the transaction group to the network and waits until it's committed to a block. An 577 // error will be thrown if submission or execution fails. 578 // 579 // The composer's status must be SUBMITTED or lower before calling this method, since execution is 580 // only allowed once. If submission is successful, this composer's status will update to SUBMITTED. 581 // If the execution is also successful, this composer's status will update to COMMITTED. 582 // 583 // Note: a group can only be submitted again if it fails. 584 // 585 // Returns the confirmed round for this transaction, the txIDs of the submitted transactions, and an 586 // ABIResult for each method call in this group. 587 func (atc *AtomicTransactionComposer) Execute(client *algod.Client, ctx context.Context, waitRounds uint64) (ExecuteResult, error) { 588 if atc.status == COMMITTED { 589 return ExecuteResult{}, errors.New("status is already committed") 590 } 591 592 _, err := atc.Submit(client, ctx) 593 if err != nil { 594 return ExecuteResult{}, err 595 } 596 atc.status = SUBMITTED 597 598 indexToWaitFor := 0 599 numMethodCalls := 0 600 for i, txContext := range atc.txContexts { 601 if txContext.isMethodCallTx() { 602 // if there is a method call in the group, we need to query the 603 // pending tranaction endpoint for it anyway, so as an optimization 604 // we should wait for its TxID 605 if numMethodCalls == 0 { 606 indexToWaitFor = i 607 } 608 numMethodCalls += 1 609 } 610 } 611 612 groupInfo, err := WaitForConfirmation(client, atc.txContexts[indexToWaitFor].txID(), waitRounds, ctx) 613 if err != nil { 614 return ExecuteResult{}, err 615 } 616 atc.status = COMMITTED 617 618 executeResponse := ExecuteResult{ 619 ConfirmedRound: groupInfo.ConfirmedRound, 620 TxIDs: atc.getTxIDs(), 621 MethodResults: make([]ABIMethodResult, 0, numMethodCalls), 622 } 623 624 for i, txContext := range atc.txContexts { 625 // Verify method call is available. This may not be the case if the App Call Tx wasn't created 626 // by AddMethodCall(). 627 if !txContext.isMethodCallTx() { 628 continue 629 } 630 631 result := ABIMethodResult{TxID: txContext.txID(), Method: *txContext.method} 632 633 if i == indexToWaitFor { 634 result.TransactionInfo = groupInfo 635 } else { 636 methodCallInfo, _, err := client.PendingTransactionInformation(result.TxID).Do(ctx) 637 if err != nil { 638 result.DecodeError = err 639 executeResponse.MethodResults = append(executeResponse.MethodResults, result) 640 continue 641 } 642 result.TransactionInfo = methodCallInfo 643 } 644 645 if txContext.method.Returns.IsVoid() { 646 result.RawReturnValue = []byte{} 647 executeResponse.MethodResults = append(executeResponse.MethodResults, result) 648 continue 649 } 650 651 if len(result.TransactionInfo.Logs) == 0 { 652 result.DecodeError = errors.New("method call did not log a return value") 653 executeResponse.MethodResults = append(executeResponse.MethodResults, result) 654 continue 655 } 656 657 lastLog := result.TransactionInfo.Logs[len(result.TransactionInfo.Logs)-1] 658 if !bytes.HasPrefix(lastLog, abiReturnHash) { 659 result.DecodeError = errors.New("method call did not log a return value") 660 executeResponse.MethodResults = append(executeResponse.MethodResults, result) 661 continue 662 } 663 664 result.RawReturnValue = lastLog[len(abiReturnHash):] 665 666 abiType, err := txContext.method.Returns.GetTypeObject() 667 if err != nil { 668 result.DecodeError = err 669 executeResponse.MethodResults = append(executeResponse.MethodResults, result) 670 break 671 } 672 673 result.ReturnValue, result.DecodeError = abiType.Decode(result.RawReturnValue) 674 executeResponse.MethodResults = append(executeResponse.MethodResults, result) 675 } 676 677 return executeResponse, nil 678 } 679 680 // marshallAbiUint64 converts any value used to represent an ABI "uint64" into 681 // a golang uint64 682 func marshallAbiUint64(value interface{}) (uint64, error) { 683 abiType, err := abi.TypeOf("uint64") 684 if err != nil { 685 return 0, err 686 } 687 encoded, err := abiType.Encode(value) 688 if err != nil { 689 return 0, err 690 } 691 decoded, err := abiType.Decode(encoded) 692 if err != nil { 693 return 0, err 694 } 695 marshalledValue, ok := decoded.(uint64) 696 if !ok { 697 err = fmt.Errorf("Decoded value is not a uint64") 698 } 699 return marshalledValue, err 700 } 701 702 // marshallAbiAddress converts any value used to represent an ABI "address" into 703 // a golang address string 704 func marshallAbiAddress(value interface{}) (string, error) { 705 abiType, err := abi.TypeOf("address") 706 if err != nil { 707 return "", err 708 } 709 encoded, err := abiType.Encode(value) 710 if err != nil { 711 return "", err 712 } 713 decoded, err := abiType.Decode(encoded) 714 if err != nil { 715 return "", err 716 } 717 marshalledValue, ok := decoded.([]byte) 718 if !ok || len(marshalledValue) != len(types.ZeroAddress) { 719 err = fmt.Errorf("Decoded value is not a 32 length byte slice") 720 } 721 var addressValue types.Address 722 copy(addressValue[:], marshalledValue) 723 return addressValue.String(), err 724 } 725 726 // populateMethodCallReferenceArgs parses reference argument types and resolves them to an index 727 // into the appropriate foreign array. Their placement will be as compact as possible, which means 728 // values will be deduplicated and any value that is the sender or the current app will not be added 729 // to the foreign array. 730 func populateMethodCallReferenceArgs(sender string, currentApp uint64, types []string, values []interface{}, accounts *[]string, apps *[]uint64, assets *[]uint64) ([]int, error) { 731 resolvedIndexes := make([]int, len(types)) 732 733 for i, value := range values { 734 var resolved int 735 736 switch types[i] { 737 case abi.AccountReferenceType: 738 address, err := marshallAbiAddress(value) 739 if err != nil { 740 return nil, err 741 } 742 if address == sender { 743 resolved = 0 744 } else { 745 duplicate := false 746 for j, account := range *accounts { 747 if address == account { 748 resolved = j + 1 // + 1 because 0 is the sender 749 duplicate = true 750 break 751 } 752 } 753 if !duplicate { 754 resolved = len(*accounts) + 1 755 *accounts = append(*accounts, address) 756 } 757 } 758 case abi.ApplicationReferenceType: 759 appID, err := marshallAbiUint64(value) 760 if err != nil { 761 return nil, err 762 } 763 if appID == currentApp { 764 resolved = 0 765 } else { 766 duplicate := false 767 for j, app := range *apps { 768 if appID == app { 769 resolved = j + 1 // + 1 because 0 is the current app 770 duplicate = true 771 break 772 } 773 } 774 if !duplicate { 775 resolved = len(*apps) + 1 776 *apps = append(*apps, appID) 777 } 778 } 779 case abi.AssetReferenceType: 780 assetID, err := marshallAbiUint64(value) 781 if err != nil { 782 return nil, err 783 } 784 duplicate := false 785 for j, asset := range *assets { 786 if assetID == asset { 787 resolved = j 788 duplicate = true 789 break 790 } 791 } 792 if !duplicate { 793 resolved = len(*assets) 794 *assets = append(*assets, assetID) 795 } 796 default: 797 return nil, fmt.Errorf("Unknown reference type: %s", types[i]) 798 } 799 800 resolvedIndexes[i] = resolved 801 } 802 803 return resolvedIndexes, nil 804 }