github.com/algorand/go-algorand-sdk@v1.24.0/test/applications_integration_test.go (about) 1 package test 2 3 import ( 4 "bytes" 5 "context" 6 "crypto/sha512" 7 "encoding/base64" 8 "encoding/binary" 9 "encoding/json" 10 "fmt" 11 "reflect" 12 "regexp" 13 "sort" 14 "strconv" 15 "strings" 16 "time" 17 18 "github.com/cucumber/godog" 19 20 "github.com/algorand/go-algorand-sdk/abi" 21 "github.com/algorand/go-algorand-sdk/client/v2/algod" 22 "github.com/algorand/go-algorand-sdk/client/v2/common/models" 23 "github.com/algorand/go-algorand-sdk/client/v2/indexer" 24 "github.com/algorand/go-algorand-sdk/crypto" 25 sdkJson "github.com/algorand/go-algorand-sdk/encoding/json" 26 "github.com/algorand/go-algorand-sdk/future" 27 "github.com/algorand/go-algorand-sdk/types" 28 ) 29 30 var algodV2client *algod.Client 31 var indexerV2client *indexer.Client 32 var tx types.Transaction 33 var transientAccount crypto.Account 34 var applicationId uint64 35 var applicationIds []uint64 36 var txComposerResult future.ExecuteResult 37 38 func anAlgodVClientConnectedToPortWithToken(v int, host string, port int, token string) error { 39 var err error 40 portAsString := strconv.Itoa(port) 41 fullHost := "http://" + host + ":" + portAsString 42 algodV2client, err = algod.MakeClient(fullHost, token) 43 aclv2 = algodV2client 44 gh = []byte("MLWBXKMRJ5W3USARAFOHPQJAF4DN6KY3ZJVPIXKODKNN5ZXSZ2DQ") 45 46 return err 47 } 48 49 func iCreateANewTransientAccountAndFundItWithMicroalgos(microalgos int) error { 50 var err error 51 52 transientAccount = crypto.GenerateAccount() 53 54 params, err := algodV2client.SuggestedParams().Do(context.Background()) 55 if err != nil { 56 return err 57 } 58 59 params.Fee = types.MicroAlgos(fee) 60 ltxn, err := future.MakePaymentTxn(accounts[1], transientAccount.Address.String(), uint64(microalgos), note, close, params) 61 if err != nil { 62 return err 63 } 64 lsk, err := kcl.ExportKey(handle, walletPswd, accounts[1]) 65 if err != nil { 66 return err 67 } 68 ltxid, lstx, err := crypto.SignTransaction(lsk.PrivateKey, ltxn) 69 if err != nil { 70 return err 71 } 72 _, err = algodV2client.SendRawTransaction(lstx).Do(context.Background()) 73 if err != nil { 74 return err 75 } 76 _, err = future.WaitForConfirmation(algodV2client, ltxid, 1, context.Background()) 77 if err != nil { 78 return err 79 } 80 return nil 81 } 82 83 func iFundTheCurrentApplicationsAddress(microalgos int) error { 84 address := crypto.GetApplicationAddress(applicationId) 85 86 params, err := algodV2client.SuggestedParams().Do(context.Background()) 87 if err != nil { 88 return err 89 } 90 91 txn, err := future.MakePaymentTxn(accounts[0], address.String(), uint64(microalgos), nil, "", params) 92 if err != nil { 93 return err 94 } 95 96 res, err := kcl.SignTransaction(handle, walletPswd, txn) 97 if err != nil { 98 return err 99 } 100 101 txid, err = algodV2client.SendRawTransaction(res.SignedTransaction).Do(context.Background()) 102 if err != nil { 103 return err 104 } 105 106 _, err = future.WaitForConfirmation(algodV2client, txid, 1, context.Background()) 107 return err 108 } 109 110 func readTealProgram(fileName string) ([]byte, error) { 111 fileContents, err := loadResource(fileName) 112 if err != nil { 113 return nil, err 114 } 115 116 if strings.HasSuffix(fileName, ".teal") { 117 // need to compile TEAL source file 118 response, err := algodV2client.TealCompile(fileContents).Do(context.Background()) 119 if err != nil { 120 return nil, err 121 } 122 return base64.StdEncoding.DecodeString(response.Result) 123 } 124 125 return fileContents, nil 126 } 127 128 func iBuildAnApplicationTransaction( 129 operation, approvalProgram, clearProgram string, 130 globalBytes, globalInts, localBytes, localInts int, 131 appArgs, foreignApps, foreignAssets, appAccounts string, 132 extraPages int, boxes string) error { 133 134 var clearP []byte 135 var approvalP []byte 136 var err error 137 138 var ghbytes [32]byte 139 copy(ghbytes[:], gh) 140 141 var suggestedParams types.SuggestedParams 142 suggestedParams, err = algodV2client.SuggestedParams().Do(context.Background()) 143 if err != nil { 144 return err 145 } 146 147 if approvalProgram != "" { 148 approvalP, err = readTealProgram(approvalProgram) 149 if err != nil { 150 return err 151 } 152 } 153 154 if clearProgram != "" { 155 clearP, err = readTealProgram(clearProgram) 156 if err != nil { 157 return err 158 } 159 } 160 args, err := parseAppArgs(appArgs) 161 if err != nil { 162 return err 163 } 164 var accs []string 165 if appAccounts != "" { 166 accs = strings.Split(appAccounts, ",") 167 } 168 fApp, err := splitUint64(foreignApps) 169 if err != nil { 170 return err 171 } 172 173 fAssets, err := splitUint64(foreignAssets) 174 if err != nil { 175 return err 176 } 177 178 staticBoxes, err := parseBoxes(boxes) 179 if err != nil { 180 return err 181 } 182 183 gSchema := types.StateSchema{NumUint: uint64(globalInts), NumByteSlice: uint64(globalBytes)} 184 lSchema := types.StateSchema{NumUint: uint64(localInts), NumByteSlice: uint64(localBytes)} 185 switch operation { 186 case "create": 187 tx, err = future.MakeApplicationCreateTxWithBoxes(false, approvalP, clearP, 188 gSchema, lSchema, uint32(extraPages), args, accs, fApp, fAssets, staticBoxes, 189 suggestedParams, transientAccount.Address, nil, types.Digest{}, [32]byte{}, types.Address{}) 190 case "create_optin": 191 tx, err = future.MakeApplicationCreateTxWithBoxes(true, approvalP, clearP, 192 gSchema, lSchema, uint32(extraPages), args, accs, fApp, fAssets, staticBoxes, 193 suggestedParams, transientAccount.Address, nil, types.Digest{}, [32]byte{}, types.Address{}) 194 case "update": 195 tx, err = future.MakeApplicationUpdateTxWithBoxes(applicationId, args, accs, fApp, fAssets, staticBoxes, 196 approvalP, clearP, 197 suggestedParams, transientAccount.Address, nil, types.Digest{}, [32]byte{}, types.Address{}) 198 case "call": 199 tx, err = future.MakeApplicationNoOpTxWithBoxes(applicationId, args, accs, 200 fApp, fAssets, staticBoxes, 201 suggestedParams, transientAccount.Address, nil, types.Digest{}, [32]byte{}, types.Address{}) 202 case "optin": 203 tx, err = future.MakeApplicationOptInTxWithBoxes(applicationId, args, accs, fApp, fAssets, staticBoxes, 204 suggestedParams, transientAccount.Address, nil, types.Digest{}, [32]byte{}, types.Address{}) 205 case "clear": 206 tx, err = future.MakeApplicationClearStateTxWithBoxes(applicationId, args, accs, fApp, fAssets, staticBoxes, 207 suggestedParams, transientAccount.Address, nil, types.Digest{}, [32]byte{}, types.Address{}) 208 case "closeout": 209 tx, err = future.MakeApplicationCloseOutTxWithBoxes(applicationId, args, accs, fApp, fAssets, staticBoxes, 210 suggestedParams, transientAccount.Address, nil, types.Digest{}, [32]byte{}, types.Address{}) 211 case "delete": 212 tx, err = future.MakeApplicationDeleteTxWithBoxes(applicationId, args, accs, fApp, fAssets, staticBoxes, 213 suggestedParams, transientAccount.Address, nil, types.Digest{}, [32]byte{}, types.Address{}) 214 default: 215 return fmt.Errorf("unsupported tx type: %s", operation) 216 } 217 218 return err 219 } 220 221 func iSignAndSubmitTheTransactionSavingTheTxidIfThereIsAnErrorItIs(expectedErr string) error { 222 var err error 223 var lstx []byte 224 225 txid, lstx, err = crypto.SignTransaction(transientAccount.PrivateKey, tx) 226 if err != nil { 227 return err 228 } 229 txid, err = algodV2client.SendRawTransaction(lstx).Do(context.Background()) 230 if err != nil && len(expectedErr) != 0 { 231 if strings.Contains(err.Error(), expectedErr) { 232 return nil 233 } 234 } 235 return err 236 } 237 238 func iWaitForTheTransactionToBeConfirmed() error { 239 _, err := future.WaitForConfirmation(algodV2client, txid, 1, context.Background()) 240 if err != nil { 241 return err 242 } 243 return nil 244 } 245 246 func iRememberTheNewApplicationID() error { 247 response, _, err := algodV2client.PendingTransactionInformation(txid).Do(context.Background()) 248 applicationId = response.ApplicationIndex 249 applicationIds = append(applicationIds, applicationId) 250 return err 251 } 252 253 func iResetTheArrayOfApplicationIDsToRemember() error { 254 applicationIds = nil 255 return nil 256 } 257 258 func iGetTheAccountAddressForTheCurrentApp() error { 259 actual := crypto.GetApplicationAddress(applicationId) 260 261 prefix := []byte("appID") 262 preimage := make([]byte, len(prefix)+8) // 8 = number of bytes in a uint64 263 copy(preimage, prefix) 264 binary.BigEndian.PutUint64(preimage[len(prefix):], applicationId) 265 266 expected := types.Address(sha512.Sum512_256(preimage)) 267 268 if expected != actual { 269 return fmt.Errorf("Addresses do not match. Expected %s, got %s", expected.String(), actual.String()) 270 } 271 return nil 272 } 273 274 func parseAppArgs(appArgsString string) (appArgs [][]byte, err error) { 275 if appArgsString == "" { 276 return make([][]byte, 0), nil 277 } 278 argsArray := strings.Split(appArgsString, ",") 279 resp := make([][]byte, len(argsArray)) 280 281 for idx, arg := range argsArray { 282 typeArg := strings.Split(arg, ":") 283 switch typeArg[0] { 284 case "str": 285 resp[idx] = []byte(typeArg[1]) 286 case "int": 287 intval, _ := strconv.ParseUint(typeArg[1], 10, 64) 288 289 buf := new(bytes.Buffer) 290 err := binary.Write(buf, binary.BigEndian, intval) 291 if err != nil { 292 return nil, fmt.Errorf("failed to convert %s to bytes", arg) 293 } 294 resp[idx] = buf.Bytes() 295 case "b64": 296 d, err := (base64.StdEncoding.DecodeString(typeArg[1])) 297 if err != nil { 298 return nil, fmt.Errorf("failed to b64 decode arg = %s", arg) 299 } 300 resp[idx] = d 301 default: 302 return nil, fmt.Errorf("Applications doesn't currently support argument of type %s", typeArg[0]) 303 } 304 } 305 return resp, err 306 } 307 308 func parseBoxes(boxesStr string) (staticBoxes []types.AppBoxReference, err error) { 309 if boxesStr == "" { 310 return make([]types.AppBoxReference, 0), nil 311 } 312 313 boxesArray := strings.Split(boxesStr, ",") 314 315 for i := 0; i < len(boxesArray); i += 2 { 316 appID, err := strconv.ParseUint(boxesArray[i], 10, 64) 317 if err != nil { 318 return nil, err 319 } 320 321 var nameBytes []byte 322 nameArray := strings.Split(boxesArray[i+1], ":") 323 if nameArray[0] == "str" { 324 nameBytes = []byte(nameArray[1]) 325 } else { 326 nameBytes, err = base64.StdEncoding.DecodeString(nameArray[1]) 327 if err != nil { 328 return nil, err 329 } 330 } 331 332 if len(nameBytes) == 0 { 333 nameBytes = nil 334 } 335 336 staticBoxes = append(staticBoxes, 337 types.AppBoxReference{ 338 AppID: appID, 339 Name: nameBytes, 340 }) 341 } 342 343 return 344 } 345 346 func splitUint64(uint64s string) ([]uint64, error) { 347 if uint64s == "" { 348 return make([]uint64, 0), nil 349 } 350 uintarr := strings.Split(uint64s, ",") 351 resp := make([]uint64, len(uintarr)) 352 var err error 353 for idx, val := range uintarr { 354 resp[idx], err = strconv.ParseUint(val, 10, 64) 355 if err != nil { 356 return nil, err 357 } 358 } 359 return resp, nil 360 } 361 362 func theTransientAccountShouldHave(appCreated string, byteSlices, uints int, 363 applicationState, key, value string) error { 364 365 acct, err := algodV2client.AccountInformation(transientAccount.Address.String()).Do(context.Background()) 366 if err != nil { 367 return err 368 } 369 if acct.AppsTotalSchema.NumByteSlice != uint64(byteSlices) { 370 return fmt.Errorf("expected NumByteSlices %d, got %d", 371 byteSlices, acct.AppsTotalSchema.NumByteSlice) 372 } 373 if acct.AppsTotalSchema.NumUint != uint64(uints) { 374 return fmt.Errorf("expected NumUnit %d, got %d", 375 uints, acct.AppsTotalSchema.NumUint) 376 } 377 created, err := strconv.ParseBool(appCreated) 378 if err != nil { 379 return err 380 } 381 382 // If we don't expect the app to exist, verify that it isn't there and exit. 383 if !created { 384 for _, ap := range acct.CreatedApps { 385 if ap.Id == applicationId { 386 return fmt.Errorf("AppId is not expected to be in the account") 387 } 388 } 389 return nil 390 } 391 392 appIdFound := false 393 for _, ap := range acct.CreatedApps { 394 if ap.Id == applicationId { 395 appIdFound = true 396 break 397 } 398 } 399 if !appIdFound { 400 return fmt.Errorf("AppId %d is not found in the account", applicationId) 401 } 402 403 // If there is no key to check, we're done. 404 if key == "" { 405 return nil 406 } 407 408 // Verify the key-value is set 409 found := false 410 var keyValues []models.TealKeyValue 411 412 switch strings.ToLower(applicationState) { 413 case "local": 414 count := 0 415 for _, als := range acct.AppsLocalState { 416 if als.Id == applicationId { 417 keyValues = als.KeyValue 418 count++ 419 } 420 } 421 if count != 1 { 422 return fmt.Errorf("Expected only one matching AppsLocalState, found %d", 423 count) 424 } 425 case "global": 426 count := 0 427 for _, als := range acct.CreatedApps { 428 if als.Id == applicationId { 429 keyValues = als.Params.GlobalState 430 count++ 431 } 432 } 433 if count != 1 { 434 return fmt.Errorf("Expected only one matching CreatedApps, found %d", 435 count) 436 } 437 default: 438 return fmt.Errorf("Unknown application state: %s", applicationState) 439 } 440 441 if len(keyValues) == 0 { 442 return fmt.Errorf("Expected keyvalues length to be greater than 0") 443 } 444 for _, kv := range keyValues { 445 if kv.Key == key { 446 if kv.Value.Type == 1 { 447 if kv.Value.Bytes != value { 448 return fmt.Errorf("Value mismatch: expected: '%s', got '%s'", 449 value, kv.Value.Bytes) 450 } 451 } else if kv.Value.Type == 0 { 452 val, err := strconv.ParseUint(value, 10, 64) 453 if err != nil { 454 return err 455 } 456 if kv.Value.Uint != val { 457 return fmt.Errorf("Value mismatch: expected: '%s', got '%s'", 458 value, kv.Value.Bytes) 459 } 460 } 461 found = true 462 } 463 } 464 if !found { 465 return fmt.Errorf("Could not find key '%s'", key) 466 } 467 return nil 468 } 469 470 func suggestedParamsAlgodV2() error { 471 var err error 472 sugParams, err = aclv2.SuggestedParams().Do(context.Background()) 473 return err 474 } 475 476 func iAddTheCurrentTransactionWithSignerToTheComposer() error { 477 err := txComposer.AddTransaction(accountTxAndSigner) 478 return err 479 } 480 481 func iCloneTheComposer() error { 482 txComposer = txComposer.Clone() 483 return nil 484 } 485 486 func iExecuteTheCurrentTransactionGroupWithTheComposer() error { 487 var err error 488 txComposerResult, err = txComposer.Execute(algodV2client, context.Background(), 10) 489 return err 490 } 491 492 func theAppShouldHaveReturned(commaSeparatedB64Results string) error { 493 b64ExpectedResults := strings.Split(commaSeparatedB64Results, ",") 494 495 if len(b64ExpectedResults) != len(txComposerResult.MethodResults) { 496 return fmt.Errorf("length of expected results doesn't match actual: %d != %d", len(b64ExpectedResults), len(txComposerResult.MethodResults)) 497 } 498 499 for i, b64ExpectedResult := range b64ExpectedResults { 500 expectedResult, err := base64.StdEncoding.DecodeString(b64ExpectedResult) 501 if err != nil { 502 return err 503 } 504 505 actualResult := txComposerResult.MethodResults[i] 506 method := actualResult.Method 507 508 if actualResult.DecodeError != nil { 509 return actualResult.DecodeError 510 } 511 512 if !bytes.Equal(actualResult.RawReturnValue, expectedResult) { 513 return fmt.Errorf("Actual result does not match expected result. Actual: %s\n", base64.StdEncoding.EncodeToString(actualResult.RawReturnValue)) 514 } 515 516 if method.Returns.IsVoid() { 517 if len(expectedResult) > 0 { 518 return fmt.Errorf("Expected result should be empty") 519 } 520 continue 521 } 522 523 abiReturnType, err := method.Returns.GetTypeObject() 524 if err != nil { 525 return err 526 } 527 528 expectedValue, err := abiReturnType.Decode(expectedResult) 529 if err != nil { 530 return err 531 } 532 533 if !reflect.DeepEqual(expectedValue, actualResult.ReturnValue) { 534 return fmt.Errorf("the decoded expected value doesn't match the actual result") 535 } 536 } 537 538 return nil 539 } 540 541 func theAppShouldHaveReturnedABITypes(colonSeparatedExpectedTypeStrings string) error { 542 expectedTypeStrings := strings.Split(colonSeparatedExpectedTypeStrings, ":") 543 544 if len(expectedTypeStrings) != len(txComposerResult.MethodResults) { 545 return fmt.Errorf("length of expected results doesn't match actual: %d != %d", len(expectedTypeStrings), len(txComposerResult.MethodResults)) 546 } 547 548 for i, expectedTypeString := range expectedTypeStrings { 549 actualResult := txComposerResult.MethodResults[i] 550 551 if actualResult.DecodeError != nil { 552 return actualResult.DecodeError 553 } 554 555 if expectedTypeString == abi.VoidReturnType { 556 if len(actualResult.RawReturnValue) != 0 { 557 return fmt.Errorf("No return bytes were expected, but some are present") 558 } 559 continue 560 } 561 562 expectedType, err := abi.TypeOf(expectedTypeString) 563 if err != nil { 564 return err 565 } 566 567 decoded, err := expectedType.Decode(actualResult.RawReturnValue) 568 if err != nil { 569 return err 570 } 571 reencoded, err := expectedType.Encode(decoded) 572 if err != nil { 573 return err 574 } 575 576 if !bytes.Equal(reencoded, actualResult.RawReturnValue) { 577 return fmt.Errorf("The round trip result does not match the original result") 578 } 579 } 580 581 return nil 582 } 583 584 func checkAtomicResultAgainstValue(resultIndex int, path, expectedValue string) error { 585 keys := strings.Split(path, ".") 586 info := txComposerResult.MethodResults[resultIndex].TransactionInfo 587 588 jsonBytes := sdkJson.Encode(&info) 589 590 var genericJson interface{} 591 decoder := json.NewDecoder(bytes.NewReader(jsonBytes)) 592 decoder.UseNumber() 593 err := decoder.Decode(&genericJson) 594 if err != nil { 595 return err 596 } 597 598 for i, key := range keys { 599 var value interface{} 600 601 intKey, err := strconv.Atoi(key) 602 if err == nil { 603 // key is an array index 604 genericArray, ok := genericJson.([]interface{}) 605 if !ok { 606 return fmt.Errorf("Path component %d is an array index (%d), but the parent is not an array. Parent type: %s", i, intKey, reflect.TypeOf(genericJson)) 607 } 608 if intKey < 0 || intKey >= len(genericArray) { 609 return fmt.Errorf("Path component %d is an array index (%d) outside of the parent array's range. Parent length: %d", i, intKey, len(genericArray)) 610 } 611 value = genericArray[intKey] 612 } else { 613 // key is an object field 614 genericObject, ok := genericJson.(map[string]interface{}) 615 if !ok { 616 return fmt.Errorf("Path component %d is an object field ('%s'), but the parent is not an object. Parent type: %s", i, key, reflect.TypeOf(genericJson)) 617 } 618 value, ok = genericObject[key] 619 if !ok { 620 var parentFields []string 621 for field := range genericObject { 622 parentFields = append(parentFields, "'"+field+"'") 623 } 624 return fmt.Errorf("Path component %d is an object field ('%s'), but the parent does not contain the field. Parent fields are: %s", i, key, strings.Join(parentFields, ",")) 625 } 626 } 627 628 genericJson = value 629 } 630 631 // we have arrived at the target object 632 switch actual := genericJson.(type) { 633 case string: 634 if actual != expectedValue { 635 return fmt.Errorf("String values not equal. Expected '%s', got '%s'", expectedValue, actual) 636 } 637 case json.Number: 638 actualParsed, err := strconv.ParseUint(actual.String(), 10, 64) 639 if err != nil { 640 return err 641 } 642 expectedParsed, err := strconv.ParseUint(expectedValue, 10, 64) 643 if err != nil { 644 return err 645 } 646 if actualParsed != expectedParsed { 647 return fmt.Errorf("Int values not equal. Expected %d, got %d", expectedParsed, actualParsed) 648 } 649 default: 650 return fmt.Errorf("The final path element does not resolve to a support type. Type is %s", reflect.TypeOf(genericJson)) 651 } 652 653 return nil 654 } 655 656 func checkInnerTxnGroupIDs(colonSeparatedPathsString string) error { 657 paths := [][]int{} 658 659 commaSeparatedPathStrings := strings.Split(colonSeparatedPathsString, ":") 660 for _, commaSeparatedPathString := range commaSeparatedPathStrings { 661 pathOfStrings := strings.Split(commaSeparatedPathString, ",") 662 path := make([]int, len(pathOfStrings)) 663 for i, stringComponent := range pathOfStrings { 664 intComponent, err := strconv.Atoi(stringComponent) 665 if err != nil { 666 return err 667 } 668 path[i] = intComponent 669 } 670 paths = append(paths, path) 671 } 672 673 var txInfosToCheck []models.PendingTransactionResponse 674 675 for _, path := range paths { 676 var current models.PendingTransactionResponse 677 for pathIndex, innerTxnIndex := range path { 678 if pathIndex == 0 { 679 current = models.PendingTransactionResponse(txComposerResult.MethodResults[innerTxnIndex].TransactionInfo) 680 } else { 681 current = current.InnerTxns[innerTxnIndex] 682 } 683 } 684 txInfosToCheck = append(txInfosToCheck, current) 685 } 686 687 var group types.Digest 688 for i, txInfo := range txInfosToCheck { 689 if i == 0 { 690 group = txInfo.Transaction.Txn.Group 691 } 692 693 if group != txInfo.Transaction.Txn.Group { 694 return fmt.Errorf("Group hashes differ: %s != %s", group, txInfo.Transaction.Txn.Group) 695 } 696 } 697 698 return nil 699 } 700 701 func checkSpinResult(resultIndex int, method, r string) error { 702 if method != "spin()" { 703 return fmt.Errorf("Incorrect method name, expected 'spin()', got '%s'", method) 704 } 705 706 result := txComposerResult.MethodResults[resultIndex] 707 decodedResult := result.ReturnValue.([]interface{}) 708 709 spin := decodedResult[0].([]interface{}) 710 spinBytes := make([]byte, len(spin)) 711 for i, value := range spin { 712 spinBytes[i] = value.(byte) 713 } 714 715 matched, err := regexp.Match(r, spinBytes) 716 if err != nil { 717 return err 718 } 719 720 if !matched { 721 return fmt.Errorf("Result did not match regex. Spin result: %s", string(spinBytes)) 722 } 723 724 return nil 725 } 726 727 func sha512_256AsUint64(preimage []byte) uint64 { 728 hashed := sha512.Sum512_256(preimage) 729 return binary.BigEndian.Uint64(hashed[:8]) 730 } 731 732 func checkRandomIntResult(resultIndex, input int) error { 733 result := txComposerResult.MethodResults[resultIndex] 734 decodedResult := result.ReturnValue.([]interface{}) 735 736 randInt := decodedResult[0].(uint64) 737 738 witness := decodedResult[1].([]interface{}) 739 witnessBytes := make([]byte, len(witness)) 740 for i, value := range witness { 741 witnessBytes[i] = value.(byte) 742 } 743 744 x := sha512_256AsUint64(witnessBytes) 745 quotient := x % uint64(input) 746 if quotient != randInt { 747 return fmt.Errorf("Unexpected result: quotient is %d and randInt is %d", quotient, randInt) 748 } 749 750 return nil 751 } 752 753 func checkRandomElementResult(resultIndex int, input string) error { 754 result := txComposerResult.MethodResults[resultIndex] 755 decodedResult := result.ReturnValue.([]interface{}) 756 757 randElt := decodedResult[0].(byte) 758 759 witness := decodedResult[1].([]interface{}) 760 witnessBytes := make([]byte, len(witness)) 761 for i, value := range witness { 762 witnessBytes[i] = value.(byte) 763 } 764 765 x := sha512_256AsUint64(witnessBytes) 766 quotient := x % uint64(len(input)) 767 if input[quotient] != randElt { 768 return fmt.Errorf("Unexpected result: input[quotient] is %d and randElt is %d", input[quotient], randElt) 769 } 770 771 return nil 772 } 773 774 func theContentsOfTheBoxWithNameShouldBeIfThereIsAnErrorItIs(fromClient, encodedBoxName, boxContents, errStr string) error { 775 var box models.Box 776 decodedBoxNames, err := parseAppArgs(encodedBoxName) 777 if err != nil { 778 return err 779 } 780 decodedBoxName := decodedBoxNames[0] 781 if fromClient == "algod" { 782 box, err = algodV2client.GetApplicationBoxByName(applicationId, decodedBoxName).Do(context.Background()) 783 } else if fromClient == "indexer" { 784 box, err = indexerV2client.LookupApplicationBoxByIDAndName(applicationId, decodedBoxName).Do(context.Background()) 785 } else { 786 err = fmt.Errorf("expecting algod or indexer, got " + fromClient) 787 } 788 if err != nil { 789 if strings.Contains(err.Error(), errStr) { 790 return nil 791 } 792 return err 793 } 794 795 b64Value := base64.StdEncoding.EncodeToString(box.Value) 796 if b64Value != boxContents { 797 return fmt.Errorf("expected box value %s is not equal to actual box value %s", boxContents, b64Value) 798 } 799 800 return nil 801 } 802 803 func currentApplicationShouldHaveFollowingBoxes(fromClient, encodedBoxesRaw string) error { 804 var expectedNames [][]byte 805 if len(encodedBoxesRaw) > 0 { 806 encodedBoxes := strings.Split(encodedBoxesRaw, ":") 807 expectedNames = make([][]byte, len(encodedBoxes)) 808 for i, b := range encodedBoxes { 809 expected, err := base64.StdEncoding.DecodeString(b) 810 if err != nil { 811 return err 812 } 813 expectedNames[i] = expected 814 } 815 } 816 sort.Slice(expectedNames, func(i, j int) bool { 817 return bytes.Compare(expectedNames[i], expectedNames[j]) < 0 818 }) 819 820 var r models.BoxesResponse 821 var err error 822 823 if fromClient == "algod" { 824 r, err = algodV2client.GetApplicationBoxes(applicationId).Do(context.Background()) 825 } else if fromClient == "indexer" { 826 r, err = indexerV2client.SearchForApplicationBoxes(applicationId).Do(context.Background()) 827 } else { 828 err = fmt.Errorf("expecting algod or indexer, got " + fromClient) 829 } 830 if err != nil { 831 return err 832 } 833 834 actualNames := make([][]byte, len(r.Boxes)) 835 for i, b := range r.Boxes { 836 actualNames[i] = b.Name 837 } 838 sort.Slice(actualNames, func(i, j int) bool { 839 return bytes.Compare(actualNames[i], actualNames[j]) < 0 840 }) 841 842 err = sliceOfBytesEqual(expectedNames, actualNames) 843 if err != nil { 844 return fmt.Errorf("expected and actual box names do not match: %w", err) 845 } 846 847 return nil 848 } 849 850 func sleptForNMilliSecsForIndexer(n int) error { 851 time.Sleep(time.Duration(n) * time.Millisecond) 852 return nil 853 } 854 855 func currentApplicationShouldHaveBoxNum(fromClient string, max int, expectedNum int) error { 856 var r models.BoxesResponse 857 var err error 858 859 if fromClient == "algod" { 860 r, err = algodV2client.GetApplicationBoxes(applicationId).Max(uint64(max)).Do(context.Background()) 861 } else if fromClient == "indexer" { 862 r, err = indexerV2client.SearchForApplicationBoxes(applicationId).Limit(uint64(max)).Do(context.Background()) 863 } else { 864 err = fmt.Errorf("expecting algod or indexer, got " + fromClient) 865 } 866 if err != nil { 867 return err 868 } 869 870 if len(r.Boxes) != expectedNum { 871 return fmt.Errorf("expected and actual box number do not match: %v != %v", expectedNum, len(r.Boxes)) 872 } 873 return nil 874 } 875 876 func indexerSaysCurrentAppShouldHaveTheseBoxes(max int, next string, encodedBoxesRaw string) error { 877 r, err := indexerV2client.SearchForApplicationBoxes(applicationId).Limit(uint64(max)).Next(next).Do(context.Background()) 878 if err != nil { 879 return err 880 } 881 actualNames := make([][]byte, len(r.Boxes)) 882 for i, b := range r.Boxes { 883 actualNames[i] = b.Name 884 } 885 sort.Slice(actualNames, func(i, j int) bool { 886 return bytes.Compare(actualNames[i], actualNames[j]) < 0 887 }) 888 889 var expectedNames [][]byte 890 if len(encodedBoxesRaw) > 0 { 891 encodedBoxes := strings.Split(encodedBoxesRaw, ":") 892 expectedNames = make([][]byte, len(encodedBoxes)) 893 for i, b := range encodedBoxes { 894 expected, err := base64.StdEncoding.DecodeString(b) 895 if err != nil { 896 return err 897 } 898 expectedNames[i] = expected 899 } 900 } 901 sort.Slice(expectedNames, func(i, j int) bool { 902 return bytes.Compare(expectedNames[i], expectedNames[j]) < 0 903 }) 904 905 err = sliceOfBytesEqual(expectedNames, actualNames) 906 if err != nil { 907 return fmt.Errorf("expected and actual box names do not match: %w", err) 908 } 909 910 return nil 911 } 912 913 // @applications.verified and @applications.boxes 914 func ApplicationsContext(s *godog.Suite) { 915 s.Step(`^an algod v(\d+) client connected to "([^"]*)" port (\d+) with token "([^"]*)"$`, anAlgodVClientConnectedToPortWithToken) 916 s.Step(`^I create a new transient account and fund it with (\d+) microalgos\.$`, iCreateANewTransientAccountAndFundItWithMicroalgos) 917 s.Step(`^I build an application transaction with the transient account, the current application, suggested params, operation "([^"]*)", approval-program "([^"]*)", clear-program "([^"]*)", global-bytes (\d+), global-ints (\d+), local-bytes (\d+), local-ints (\d+), app-args "([^"]*)", foreign-apps "([^"]*)", foreign-assets "([^"]*)", app-accounts "([^"]*)", extra-pages (\d+), boxes "([^"]*)"$`, iBuildAnApplicationTransaction) 918 s.Step(`^I sign and submit the transaction, saving the txid\. If there is an error it is "([^"]*)"\.$`, iSignAndSubmitTheTransactionSavingTheTxidIfThereIsAnErrorItIs) 919 s.Step(`^I wait for the transaction to be confirmed\.$`, iWaitForTheTransactionToBeConfirmed) 920 s.Step(`^I remember the new application ID\.$`, iRememberTheNewApplicationID) 921 s.Step(`^I reset the array of application IDs to remember\.$`, iResetTheArrayOfApplicationIDsToRemember) 922 s.Step(`^I get the account address for the current application and see that it matches the app id\'s hash$`, iGetTheAccountAddressForTheCurrentApp) 923 s.Step(`^The transient account should have the created app "([^"]*)" and total schema byte-slices (\d+) and uints (\d+), the application "([^"]*)" state contains key "([^"]*)" with value "([^"]*)"$`, 924 theTransientAccountShouldHave) 925 s.Step(`^suggested transaction parameters from the algod v2 client$`, suggestedParamsAlgodV2) 926 s.Step(`^I add the current transaction with signer to the composer\.$`, iAddTheCurrentTransactionWithSignerToTheComposer) 927 s.Step(`^I clone the composer\.$`, iCloneTheComposer) 928 s.Step(`^I execute the current transaction group with the composer\.$`, iExecuteTheCurrentTransactionGroupWithTheComposer) 929 s.Step(`^The app should have returned "([^"]*)"\.$`, theAppShouldHaveReturned) 930 s.Step(`^The app should have returned ABI types "([^"]*)"\.$`, theAppShouldHaveReturnedABITypes) 931 932 s.Step(`^I fund the current application\'s address with (\d+) microalgos\.$`, iFundTheCurrentApplicationsAddress) 933 934 s.Step(`^I can dig the (\d+)th atomic result with path "([^"]*)" and see the value "([^"]*)"$`, checkAtomicResultAgainstValue) 935 s.Step(`^I dig into the paths "([^"]*)" of the resulting atomic transaction tree I see group ids and they are all the same$`, checkInnerTxnGroupIDs) 936 s.Step(`^The (\d+)th atomic result for "([^"]*)" satisfies the regex "([^"]*)"$`, checkSpinResult) 937 938 s.Step(`^The (\d+)th atomic result for randomInt\((\d+)\) proves correct$`, checkRandomIntResult) 939 s.Step(`^The (\d+)th atomic result for randElement\("([^"]*)"\) proves correct$`, checkRandomElementResult) 940 941 s.Step(`^according to "([^"]*)", the contents of the box with name "([^"]*)" in the current application should be "([^"]*)"\. If there is an error it is "([^"]*)"\.$`, theContentsOfTheBoxWithNameShouldBeIfThereIsAnErrorItIs) 942 s.Step(`^according to "([^"]*)", the current application should have the following boxes "([^"]*)"\.$`, currentApplicationShouldHaveFollowingBoxes) 943 s.Step(`^according to "([^"]*)", with (\d+) being the parameter that limits results, the current application should have (\d+) boxes\.$`, currentApplicationShouldHaveBoxNum) 944 s.Step(`^according to indexer, with (\d+) being the parameter that limits results, and "([^"]*)" being the parameter that sets the next result, the current application should have the following boxes "([^"]*)"\.$`, indexerSaysCurrentAppShouldHaveTheseBoxes) 945 s.Step(`^I sleep for (\d+) milliseconds for indexer to digest things down\.$`, sleptForNMilliSecsForIndexer) 946 }