diff --git a/.circleci/config.yml b/.circleci/config.yml
index 27c6164366..9c58e88c30 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -197,7 +197,7 @@ commands:
shell: bash.exe
command: |
choco install -y msys2 pacman make wget --force
- choco install -y golang --version=1.14.7 --force
+ choco install -y golang --version=1.16.11 --force
choco install -y python3 --version=3.7.3 --force
export msys2='cmd //C RefreshEnv.cmd '
export msys2+='& set MSYS=winsymlinks:nativestrict '
@@ -322,7 +322,6 @@ commands:
export PACKAGE_NAMES=$(echo $PACKAGES | tr -d '\n')
export PARTITION_TOTAL=${CIRCLE_NODE_TOTAL}
export PARTITION_ID=${CIRCLE_NODE_INDEX}
- export PARALLEL_FLAG="-p 1"
gotestsum --format testname --junitfile << parameters.result_path >>/<< parameters.result_subdir >>/${CIRCLE_NODE_INDEX}/results.xml --jsonfile << parameters.result_path >>/<< parameters.result_subdir >>/${CIRCLE_NODE_INDEX}/testresults.json -- --tags "sqlite_unlock_notify sqlite_omit_load_extension" << parameters.short_test_flag >> -race -timeout 1h -coverprofile=coverage.txt -covermode=atomic -p 1 $PACKAGE_NAMES
- store_artifacts:
path: << parameters.result_path >>
@@ -432,6 +431,7 @@ commands:
export TEST_RESULTS=<< parameters.result_path >>/<< parameters.result_subdir >>/${CIRCLE_NODE_INDEX}
export PARTITION_TOTAL=${CIRCLE_NODE_TOTAL}
export PARTITION_ID=${CIRCLE_NODE_INDEX}
+ export PARALLEL_FLAG="-p 1"
test/scripts/run_integration_tests.sh
- store_artifacts:
path: << parameters.result_path >>
diff --git a/.github/workflows/reviewdog.yml b/.github/workflows/reviewdog.yml
index 30b76064bf..8813920f37 100644
--- a/.github/workflows/reviewdog.yml
+++ b/.github/workflows/reviewdog.yml
@@ -44,7 +44,7 @@ jobs:
- name: Install specific golang
uses: actions/setup-go@v2
with:
- go-version: '1.16.6'
+ go-version: '1.16.11'
- name: Create folders for golangci-lint
run: mkdir -p cicdtmp/golangci-lint
- name: Check if custom golangci-lint is already built
diff --git a/.gitignore b/.gitignore
index 218b3879b8..ebd0ef239b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -65,3 +65,5 @@ assets
index.html
+# test summary
+testresults.json
diff --git a/Makefile b/Makefile
index b17010bfea..38f5a46ed8 100644
--- a/Makefile
+++ b/Makefile
@@ -8,7 +8,6 @@ else
export GOPATH := $(shell go env GOPATH)
GOPATH1 := $(firstword $(subst :, ,$(GOPATH)))
endif
-export GO111MODULE := on
export GOPROXY := direct
SRCPATH := $(shell pwd)
ARCH := $(shell ./scripts/archtype.sh)
diff --git a/README.md b/README.md
index 1f9045adf1..a5c0487c35 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,5 @@
-[](https://travis-ci.com/algorand/go-algorand)
+| rel/stable
[](https://circleci.com/gh/algorand/go-algorand/tree/rel%2Fstable) | rel/beta
[](https://circleci.com/gh/algorand/go-algorand/tree/rel%2Fbeta) | rel/nightly
[](https://circleci.com/gh/algorand/go-algorand/tree/rel%2Fnightly) |
+| --- | --- | --- |
go-algorand
====================
diff --git a/agreement/README.md b/agreement/README.md
index d3b2e1611a..6c627a6f9b 100644
--- a/agreement/README.md
+++ b/agreement/README.md
@@ -43,7 +43,7 @@ will also recover safely after crashes.
# Specification
The specification for the protocol implemented by this package is
-located [here](https://github.com/algorand/spec).
+located [here](https://github.com/algorandfoundation/specs).
Optimizations from and other deviations from the spec will be noted
throughout this file.
diff --git a/catchup/service.go b/catchup/service.go
index 27ce957baa..ff3df36095 100644
--- a/catchup/service.go
+++ b/catchup/service.go
@@ -195,7 +195,11 @@ func (s *Service) innerFetch(r basics.Round, peer network.Peer) (blk *bookkeepin
}
// fetchAndWrite fetches a block, checks the cert, and writes it to the ledger. Cert checking and ledger writing both wait for the ledger to advance if necessary.
-// Returns false if we couldn't fetch or write (i.e., if we failed even after a given number of retries or if we were told to abort.)
+// Returns false if we should stop trying to catch up. This may occur for several reasons:
+// - If the context is canceled (e.g. if the node is shutting down)
+// - If we couldn't fetch the block (e.g. if there are no peers available or we've reached the catchupRetryLimit)
+// - If the block is already in the ledger (e.g. if agreement service has already written it)
+// - If the retrieval of the previous block was unsuccessful
func (s *Service) fetchAndWrite(r basics.Round, prevFetchCompleteChan chan bool, lookbackComplete chan bool, peerSelector *peerSelector) bool {
i := 0
hasLookback := false
@@ -241,8 +245,10 @@ func (s *Service) fetchAndWrite(r basics.Round, prevFetchCompleteChan chan bool,
if err != nil {
if err == errLedgerAlreadyHasBlock {
- // ledger already has the block, no need to request this block from anyone.
- return true
+ // ledger already has the block, no need to request this block.
+ // only the agreement could have added this block into the ledger, catchup is complete
+ s.log.Infof("fetchAndWrite(%d): the block is already in the ledger. The catchup is complete", r)
+ return false
}
s.log.Debugf("fetchAndWrite(%v): Could not fetch: %v (attempt %d)", r, err, i)
peerSelector.rankPeer(psp, peerRankDownloadFailed)
@@ -353,8 +359,10 @@ func (s *Service) fetchAndWrite(r basics.Round, prevFetchCompleteChan chan bool,
s.log.Infof("fetchAndWrite(%d): no need to re-evaluate historical block", r)
return true
case ledgercore.BlockInLedgerError:
- s.log.Infof("fetchAndWrite(%d): block already in ledger", r)
- return true
+ // the block was added to the ledger from elsewhere after fetching it here
+ // only the agreement could have added this block into the ledger, catchup is complete
+ s.log.Infof("fetchAndWrite(%d): after fetching the block, it is already in the ledger. The catchup is complete", r)
+ return false
case protocol.Error:
if !s.protocolErrorLogged {
logging.Base().Errorf("fetchAndWrite(%v): unrecoverable protocol error detected: %v", r, err)
@@ -387,7 +395,7 @@ func (s *Service) pipelineCallback(r basics.Round, thisFetchComplete chan bool,
thisFetchComplete <- fetchResult
if !fetchResult {
- s.log.Infof("failed to fetch block %v", r)
+ s.log.Infof("pipelineCallback(%d): did not fetch or write the block", r)
return 0
}
return r
diff --git a/cmd/goal/account.go b/cmd/goal/account.go
index 7d20af38f3..ccc37f227f 100644
--- a/cmd/goal/account.go
+++ b/cmd/goal/account.go
@@ -59,7 +59,7 @@ var (
partKeyOutDir string
partKeyFile string
partKeyDeleteInput bool
- listpartkeyCompat bool
+ partkeyCompat bool
importDefault bool
mnemonic string
dumpOutFile string
@@ -167,7 +167,10 @@ func init() {
installParticipationKeyCmd.Flags().BoolVar(&partKeyDeleteInput, "delete-input", false, "Acknowledge that installpartkey will delete the input key file")
// listpartkey flags
- listParticipationKeysCmd.Flags().BoolVarP(&listpartkeyCompat, "compatibility", "c", false, "Print output in compatibility mode. This option will be removed in a future release, please use REST API for tooling.")
+ listParticipationKeysCmd.Flags().BoolVarP(&partkeyCompat, "compatibility", "c", false, "Print output in compatibility mode. This option will be removed in a future release, please use REST API for tooling.")
+
+ // partkeyinfo flags
+ partkeyInfoCmd.Flags().BoolVarP(&partkeyCompat, "compatibility", "c", false, "Print output in compatibility mode. This option will be removed in a future release, please use REST API for tooling.")
// import flags
importCmd.Flags().BoolVarP(&importDefault, "default", "f", false, "Set this account as the default one")
@@ -1069,6 +1072,7 @@ func uintToStr(number uint64) string {
// legacyListParticipationKeysCommand prints key information in the same
// format as earlier versions of goal. Some users are using this information
// in scripts and need some extra time to migrate to the REST API.
+// DEPRECATED
func legacyListParticipationKeysCommand() {
dataDir := ensureSingleDataDir()
@@ -1118,7 +1122,7 @@ var listParticipationKeysCmd = &cobra.Command{
Long: `List all participation keys tracked by algod along with summary of additional information. For detailed key information use 'partkeyinfo'.`,
Args: validateNoPosArgsFn,
Run: func(cmd *cobra.Command, args []string) {
- if listpartkeyCompat {
+ if partkeyCompat {
legacyListParticipationKeysCommand()
return
}
@@ -1364,12 +1368,57 @@ func strOrNA(value *uint64) string {
return uintToStr(*value)
}
+// legacyPartkeyInfoCommand prints key information in the same
+// format as earlier versions of goal. Some users are using this information
+// in scripts and need some extra time to migrate to alternatives.
+// DEPRECATED
+func legacyPartkeyInfoCommand() {
+ type partkeyInfo struct {
+ _struct struct{} `codec:",omitempty,omitemptyarray"`
+ Address string `codec:"acct"`
+ FirstValid basics.Round `codec:"first"`
+ LastValid basics.Round `codec:"last"`
+ VoteID crypto.OneTimeSignatureVerifier `codec:"vote"`
+ SelectionID crypto.VRFVerifier `codec:"sel"`
+ VoteKeyDilution uint64 `codec:"voteKD"`
+ }
+
+ onDataDirs(func(dataDir string) {
+ fmt.Printf("Dumping participation key info from %s...\n", dataDir)
+ client := ensureGoalClient(dataDir, libgoal.DynamicClient)
+
+ // Make sure we don't already have a partkey valid for (or after) specified roundLastValid
+ parts, err := client.ListParticipationKeyFiles()
+ if err != nil {
+ reportErrorf(errorRequestFail, err)
+ }
+
+ for filename, part := range parts {
+ fmt.Println("------------------------------------------------------------------")
+ info := partkeyInfo{
+ Address: part.Address().String(),
+ FirstValid: part.FirstValid,
+ LastValid: part.LastValid,
+ VoteID: part.VotingSecrets().OneTimeSignatureVerifier,
+ SelectionID: part.VRFSecrets().PK,
+ VoteKeyDilution: part.KeyDilution,
+ }
+ infoString := protocol.EncodeJSON(&info)
+ fmt.Printf("File: %s\n%s\n", filename, string(infoString))
+ }
+ })
+}
+
var partkeyInfoCmd = &cobra.Command{
Use: "partkeyinfo",
Short: "Output details about all available part keys",
Long: `Output details about all available part keys in the specified data directory(ies), such as key validity period.`,
Args: validateNoPosArgsFn,
Run: func(cmd *cobra.Command, args []string) {
+ if partkeyCompat {
+ legacyPartkeyInfoCommand()
+ return
+ }
onDataDirs(func(dataDir string) {
fmt.Printf("Dumping participation key info from %s...\n", dataDir)
diff --git a/cmd/goal/application.go b/cmd/goal/application.go
index 2758a27125..c3439a245a 100644
--- a/cmd/goal/application.go
+++ b/cmd/goal/application.go
@@ -1047,6 +1047,119 @@ var infoAppCmd = &cobra.Command{
},
}
+// populateMethodCallTxnArgs parses and loads transactions from the files indicated by the values
+// slice. An error will occur if the transaction does not matched the expected type, it has a nonzero
+// group ID, or if it is signed by a normal signature or Msig signature (but not Lsig signature)
+func populateMethodCallTxnArgs(types []string, values []string) ([]transactions.SignedTxn, error) {
+ loadedTxns := make([]transactions.SignedTxn, len(values))
+
+ for i, txFilename := range values {
+ data, err := readFile(txFilename)
+ if err != nil {
+ return nil, fmt.Errorf(fileReadError, txFilename, err)
+ }
+
+ var txn transactions.SignedTxn
+ err = protocol.Decode(data, &txn)
+ if err != nil {
+ return nil, fmt.Errorf(txDecodeError, txFilename, err)
+ }
+
+ if !txn.Sig.Blank() || !txn.Msig.Blank() {
+ return nil, fmt.Errorf("Transaction from %s has already been signed", txFilename)
+ }
+
+ if !txn.Txn.Group.IsZero() {
+ return nil, fmt.Errorf("Transaction from %s already has a group ID: %s", txFilename, txn.Txn.Group)
+ }
+
+ expectedType := types[i]
+ if expectedType != abi.AnyTransactionType && txn.Txn.Type != protocol.TxType(expectedType) {
+ return nil, fmt.Errorf("Transaction from %s does not match method argument type. Expected %s, got %s", txFilename, expectedType, txn.Txn.Type)
+ }
+
+ loadedTxns[i] = txn
+ }
+
+ return loadedTxns, nil
+}
+
+// populateMethodCallReferenceArgs parses reference argument types and resolves them to an index
+// into the appropriate foreign array. Their placement will be as compact as possible, which means
+// values will be deduplicated and any value that is the sender or the current app will not be added
+// to the foreign array.
+func populateMethodCallReferenceArgs(sender string, currentApp uint64, types []string, values []string, accounts *[]string, apps *[]uint64, assets *[]uint64) ([]int, error) {
+ resolvedIndexes := make([]int, len(types))
+
+ for i, value := range values {
+ var resolved int
+
+ switch types[i] {
+ case abi.AccountReferenceType:
+ if value == sender {
+ resolved = 0
+ } else {
+ duplicate := false
+ for j, account := range *accounts {
+ if value == account {
+ resolved = j + 1 // + 1 because 0 is the sender
+ duplicate = true
+ break
+ }
+ }
+ if !duplicate {
+ resolved = len(*accounts) + 1
+ *accounts = append(*accounts, value)
+ }
+ }
+ case abi.ApplicationReferenceType:
+ appID, err := strconv.ParseUint(value, 10, 64)
+ if err != nil {
+ return nil, fmt.Errorf("Unable to parse application ID '%s': %s", value, err)
+ }
+ if appID == currentApp {
+ resolved = 0
+ } else {
+ duplicate := false
+ for j, app := range *apps {
+ if appID == app {
+ resolved = j + 1 // + 1 because 0 is the current app
+ duplicate = true
+ break
+ }
+ }
+ if !duplicate {
+ resolved = len(*apps) + 1
+ *apps = append(*apps, appID)
+ }
+ }
+ case abi.AssetReferenceType:
+ assetID, err := strconv.ParseUint(value, 10, 64)
+ if err != nil {
+ return nil, fmt.Errorf("Unable to parse asset ID '%s': %s", value, err)
+ }
+ duplicate := false
+ for j, asset := range *assets {
+ if assetID == asset {
+ resolved = j
+ duplicate = true
+ break
+ }
+ }
+ if !duplicate {
+ resolved = len(*assets)
+ *assets = append(*assets, assetID)
+ }
+ default:
+ return nil, fmt.Errorf("Unknown reference type: %s", types[i])
+ }
+
+ resolvedIndexes[i] = resolved
+ }
+
+ return resolvedIndexes, nil
+}
+
var methodAppCmd = &cobra.Command{
Use: "method",
Short: "Invoke a method",
@@ -1079,16 +1192,70 @@ var methodAppCmd = &cobra.Command{
applicationArgs = append(applicationArgs, hash[0:4])
// parse down the ABI type from method signature
- argTupleTypeStr, retTypeStr, err := abi.ParseMethodSignature(method)
+ _, argTypes, retTypeStr, err := abi.ParseMethodSignature(method)
if err != nil {
reportErrorf("cannot parse method signature: %v", err)
}
- err = abi.ParseArgJSONtoByteSlice(argTupleTypeStr, methodArgs, &applicationArgs)
+
+ var retType *abi.Type
+ if retTypeStr != "void" {
+ theRetType, err := abi.TypeOf(retTypeStr)
+ if err != nil {
+ reportErrorf("cannot cast %s to abi type: %v", retTypeStr, err)
+ }
+ retType = &theRetType
+ }
+
+ if len(methodArgs) != len(argTypes) {
+ reportErrorf("incorrect number of arguments, method expected %d but got %d", len(argTypes), len(methodArgs))
+ }
+
+ var txnArgTypes []string
+ var txnArgValues []string
+ var basicArgTypes []string
+ var basicArgValues []string
+ var refArgTypes []string
+ var refArgValues []string
+ refArgIndexToBasicArgIndex := make(map[int]int)
+ for i, argType := range argTypes {
+ argValue := methodArgs[i]
+ if abi.IsTransactionType(argType) {
+ txnArgTypes = append(txnArgTypes, argType)
+ txnArgValues = append(txnArgValues, argValue)
+ } else {
+ if abi.IsReferenceType(argType) {
+ refArgIndexToBasicArgIndex[len(refArgTypes)] = len(basicArgTypes)
+ refArgTypes = append(refArgTypes, argType)
+ refArgValues = append(refArgValues, argValue)
+ // treat the reference as a uint8 for encoding purposes
+ argType = "uint8"
+ }
+ basicArgTypes = append(basicArgTypes, argType)
+ basicArgValues = append(basicArgValues, argValue)
+ }
+ }
+
+ refArgsResolved, err := populateMethodCallReferenceArgs(account, appIdx, refArgTypes, refArgValues, &appAccounts, &foreignApps, &foreignAssets)
+ if err != nil {
+ reportErrorf("error populating reference arguments: %v", err)
+ }
+ for i, resolved := range refArgsResolved {
+ basicArgIndex := refArgIndexToBasicArgIndex[i]
+ // use the foreign array index as the encoded argument value
+ basicArgValues[basicArgIndex] = strconv.Itoa(resolved)
+ }
+
+ err = abi.ParseArgJSONtoByteSlice(basicArgTypes, basicArgValues, &applicationArgs)
if err != nil {
reportErrorf("cannot parse arguments to ABI encoding: %v", err)
}
- tx, err := client.MakeUnsignedApplicationCallTx(
+ txnArgs, err := populateMethodCallTxnArgs(txnArgTypes, txnArgValues)
+ if err != nil {
+ reportErrorf("error populating transaction arguments: %v", err)
+ }
+
+ appCallTxn, err := client.MakeUnsignedApplicationCallTx(
appIdx, applicationArgs, appAccounts, foreignApps, foreignAssets,
onCompletionEnum, approvalProg, clearProg, basics.StateSchema{}, basics.StateSchema{}, 0)
@@ -1097,8 +1264,8 @@ var methodAppCmd = &cobra.Command{
}
// Fill in note and lease
- tx.Note = parseNoteField(cmd)
- tx.Lease = parseLease(cmd)
+ appCallTxn.Note = parseNoteField(cmd)
+ appCallTxn.Lease = parseLease(cmd)
// Fill in rounds, fee, etc.
fv, lv, err := client.ComputeValidityRounds(firstValid, lastValid, numValidRounds)
@@ -1106,23 +1273,65 @@ var methodAppCmd = &cobra.Command{
reportErrorf("Cannot determine last valid round: %s", err)
}
- tx, err = client.FillUnsignedTxTemplate(account, fv, lv, fee, tx)
+ appCallTxn, err = client.FillUnsignedTxTemplate(account, fv, lv, fee, appCallTxn)
if err != nil {
reportErrorf("Cannot construct transaction: %s", err)
}
explicitFee := cmd.Flags().Changed("fee")
if explicitFee {
- tx.Fee = basics.MicroAlgos{Raw: fee}
+ appCallTxn.Fee = basics.MicroAlgos{Raw: fee}
}
+ // Compile group
+ var txnGroup []transactions.Transaction
+ for i := range txnArgs {
+ txnGroup = append(txnGroup, txnArgs[i].Txn)
+ }
+ txnGroup = append(txnGroup, appCallTxn)
+ if len(txnGroup) > 1 {
+ // Only if transaction arguments are present, assign group ID
+ groupID, err := client.GroupID(txnGroup)
+ if err != nil {
+ reportErrorf("Cannot assign transaction group ID: %s", err)
+ }
+ for i := range txnGroup {
+ txnGroup[i].Group = groupID
+ }
+ }
+
+ // Sign transactions
+ var signedTxnGroup []transactions.SignedTxn
+ shouldSign := sign || outFilename == ""
+ for i, unsignedTxn := range txnGroup {
+ txnFromArgs := transactions.SignedTxn{}
+ if i < len(txnArgs) {
+ txnFromArgs = txnArgs[i]
+ }
+
+ if !txnFromArgs.Lsig.Blank() {
+ signedTxnGroup = append(signedTxnGroup, transactions.SignedTxn{
+ Lsig: txnFromArgs.Lsig,
+ AuthAddr: txnFromArgs.AuthAddr,
+ Txn: unsignedTxn,
+ })
+ continue
+ }
+
+ signedTxn, err := createSignedTransaction(client, shouldSign, dataDir, walletName, unsignedTxn, txnFromArgs.AuthAddr)
+ if err != nil {
+ reportErrorf(errorSigningTX, err)
+ }
+
+ signedTxnGroup = append(signedTxnGroup, signedTxn)
+ }
+
+ // Output to file
if outFilename != "" {
if dumpForDryrun {
- err = writeDryrunReqToFile(client, tx, outFilename)
+ err = writeDryrunReqToFile(client, signedTxnGroup, outFilename)
} else {
- // Write transaction to file
- err = writeTxnToFile(client, sign, dataDir, walletName, tx, outFilename)
+ err = writeSignedTxnsToFile(signedTxnGroup, outFilename)
}
-
if err != nil {
reportErrorf(err.Error())
}
@@ -1130,19 +1339,19 @@ var methodAppCmd = &cobra.Command{
}
// Broadcast
- wh, pw := ensureWalletHandleMaybePassword(dataDir, walletName, true)
- signedTxn, err := client.SignTransactionWithWallet(wh, pw, tx)
- if err != nil {
- reportErrorf(errorSigningTX, err)
- }
-
- txid, err := client.BroadcastTransaction(signedTxn)
+ err = client.BroadcastTransactionGroup(signedTxnGroup)
if err != nil {
reportErrorf(errorBroadcastingTX, err)
}
// Report tx details to user
- reportInfof("Issued transaction from account %s, txid %s (fee %d)", tx.Sender, txid, tx.Fee.Raw)
+ reportInfof("Issued %d transaction(s):", len(signedTxnGroup))
+ // remember the final txid in this variable
+ var txid string
+ for _, stxn := range signedTxnGroup {
+ txid = stxn.Txn.ID().String()
+ reportInfof("\tIssued transaction from account %s, txid %s (fee %d)", stxn.Txn.Sender, txid, stxn.Txn.Fee.Raw)
+ }
if !noWaitAfterSend {
_, err := waitForCommit(client, txid, lv)
@@ -1155,8 +1364,8 @@ var methodAppCmd = &cobra.Command{
reportErrorf(err.Error())
}
- if retTypeStr == "void" {
- fmt.Printf("method %s succeeded", method)
+ if retType == nil {
+ fmt.Printf("method %s succeeded\n", method)
return
}
@@ -1181,10 +1390,6 @@ var methodAppCmd = &cobra.Command{
reportErrorf("cannot find return log for abi type %s", retTypeStr)
}
- retType, err := abi.TypeOf(retTypeStr)
- if err != nil {
- reportErrorf("cannot cast %s to abi type: %v", retTypeStr, err)
- }
decoded, err := retType.Decode(abiEncodedRet)
if err != nil {
reportErrorf("cannot decode return value %v: %v", abiEncodedRet, err)
@@ -1194,7 +1399,7 @@ var methodAppCmd = &cobra.Command{
if err != nil {
reportErrorf("cannot marshal returned bytes %v to JSON: %v", decoded, err)
}
- fmt.Printf("method %s succeeded with output: %s", method, string(decodedJSON))
+ fmt.Printf("method %s succeeded with output: %s\n", method, string(decodedJSON))
}
},
}
diff --git a/cmd/goal/clerk.go b/cmd/goal/clerk.go
index 71098f8a2f..3bf35f795a 100644
--- a/cmd/goal/clerk.go
+++ b/cmd/goal/clerk.go
@@ -194,34 +194,45 @@ func waitForCommit(client libgoal.Client, txid string, transactionLastValidRound
return
}
-func createSignedTransaction(client libgoal.Client, signTx bool, dataDir string, walletName string, tx transactions.Transaction) (stxn transactions.SignedTxn, err error) {
+func createSignedTransaction(client libgoal.Client, signTx bool, dataDir string, walletName string, tx transactions.Transaction, signer basics.Address) (stxn transactions.SignedTxn, err error) {
if signTx {
// Sign the transaction
wh, pw := ensureWalletHandleMaybePassword(dataDir, walletName, true)
- stxn, err = client.SignTransactionWithWallet(wh, pw, tx)
- if err != nil {
- return
- }
- } else {
- // Wrap in a transactions.SignedTxn with an empty sig.
- // This way protocol.Encode will encode the transaction type
- stxn, err = transactions.AssembleSignedTxn(tx, crypto.Signature{}, crypto.MultisigSig{})
- if err != nil {
- return
+ if signer.IsZero() {
+ stxn, err = client.SignTransactionWithWallet(wh, pw, tx)
+ } else {
+ stxn, err = client.SignTransactionWithWalletAndSigner(wh, pw, signer.String(), tx)
}
+ return
+ }
- stxn = populateBlankMultisig(client, dataDir, walletName, stxn)
+ // Wrap in a transactions.SignedTxn with an empty sig.
+ // This way protocol.Encode will encode the transaction type
+ stxn, err = transactions.AssembleSignedTxn(tx, crypto.Signature{}, crypto.MultisigSig{})
+ if err != nil {
+ return
}
+
+ stxn = populateBlankMultisig(client, dataDir, walletName, stxn)
return
}
+func writeSignedTxnsToFile(stxns []transactions.SignedTxn, filename string) error {
+ var outData []byte
+ for _, stxn := range stxns {
+ outData = append(outData, protocol.Encode(&stxn)...)
+ }
+
+ return writeFile(filename, outData, 0600)
+}
+
func writeTxnToFile(client libgoal.Client, signTx bool, dataDir string, walletName string, tx transactions.Transaction, filename string) error {
- stxn, err := createSignedTransaction(client, signTx, dataDir, walletName, tx)
+ stxn, err := createSignedTransaction(client, signTx, dataDir, walletName, tx, basics.Address{})
if err != nil {
return err
}
// Write the SignedTxn to the output file
- return writeFile(filename, protocol.Encode(&stxn), 0600)
+ return writeSignedTxnsToFile([]transactions.SignedTxn{stxn}, filename)
}
func getB64Args(args []string) [][]byte {
@@ -419,7 +430,7 @@ var sendCmd = &cobra.Command{
}
} else {
signTx := sign || (outFilename == "")
- stx, err = createSignedTransaction(client, signTx, dataDir, walletName, payment)
+ stx, err = createSignedTransaction(client, signTx, dataDir, walletName, payment, basics.Address{})
if err != nil {
reportErrorf(errorSigningTX, err)
}
@@ -856,13 +867,12 @@ var groupCmd = &cobra.Command{
transactionIdx++
}
- var outData []byte
- for _, stxn := range stxns {
- stxn.Txn.Group = crypto.HashObj(group)
- outData = append(outData, protocol.Encode(&stxn)...)
+ groupHash := crypto.HashObj(group)
+ for i := range stxns {
+ stxns[i].Txn.Group = groupHash
}
- err = writeFile(outFilename, outData, 0600)
+ err = writeSignedTxnsToFile(stxns, outFilename)
if err != nil {
reportErrorf(fileWriteError, outFilename, err)
}
diff --git a/cmd/partitiontest_linter/linter.go b/cmd/partitiontest_linter/linter.go
index 27ffbf14e2..e2086c652c 100644
--- a/cmd/partitiontest_linter/linter.go
+++ b/cmd/partitiontest_linter/linter.go
@@ -28,6 +28,7 @@ const functionName string = "PartitionTest"
const fileNameSuffix string = "_test.go"
const functionNamePrefix string = "Test"
const parameterType string = "T"
+const parameterName string = "t"
// Analyzer initilization
var Analyzer = &analysis.Analyzer{
@@ -58,7 +59,7 @@ func run(pass *analysis.Pass) (interface{}, error) {
continue
}
if !isSearchLineInFunction(fn) {
- pass.Reportf(fn.Pos(), "%s function is missing %s.%s(<%s type parameter>)", fn.Name.Name, packageName, functionName, parameterType)
+ pass.Reportf(fn.Pos(), "%s: Add missing partition call to top of test. To disable partitioning, add it as a comment: %s.%s(%s)", fn.Name.Name, packageName, functionName, parameterName)
}
}
diff --git a/config/consensus.go b/config/consensus.go
index f0ffa1a517..30cde80eed 100644
--- a/config/consensus.go
+++ b/config/consensus.go
@@ -286,6 +286,9 @@ type ConsensusParams struct {
// maximum number of inner transactions that can be created by an app call
MaxInnerTransactions int
+ // should inner transaction limit be pooled across app calls?
+ EnableInnerTransactionPooling bool
+
// maximum number of applications a single account can create and store
// AppParams for at once
MaxAppsCreated int
@@ -1053,6 +1056,7 @@ func initConsensusProtocols() {
// Enable TEAL 6 / AVM 1.1
vFuture.LogicSigVersion = 6
+ vFuture.EnableInnerTransactionPooling = true
vFuture.MaxProposedExpiredOnlineAccounts = 32
diff --git a/config/version.go b/config/version.go
index 1980378d9e..21370a4d7b 100644
--- a/config/version.go
+++ b/config/version.go
@@ -33,7 +33,7 @@ const VersionMajor = 3
// VersionMinor is the Minor semantic version number (x.#.z) - changed when backwards-compatible features are introduced.
// Not enforced until after initial public release (x > 0).
-const VersionMinor = 0
+const VersionMinor = 3
// Version is the type holding our full version information.
type Version struct {
diff --git a/daemon/algod/api/algod.oas2.json b/daemon/algod/api/algod.oas2.json
index 57b719d5a8..0eb127d8cb 100644
--- a/daemon/algod/api/algod.oas2.json
+++ b/daemon/algod/api/algod.oas2.json
@@ -463,13 +463,10 @@
}
},
"/v2/participation": {
-
"get": {
-
"tags": [
"private"
],
-
"description": "Return a list of participation keys",
"produces": [
"application/json"
@@ -513,14 +510,10 @@
}
}
},
-
-
"post": {
-
"tags": [
"private"
],
-
"consumes": [
"application/msgpack"
],
@@ -544,7 +537,6 @@
}
}
],
-
"responses": {
"200": {
"$ref": "#/responses/PostParticipationResponse"
@@ -577,22 +569,17 @@
"description": "Unknown Error"
}
}
-
}
-
},
"/v2/participation/{participation-id}": {
"delete": {
-
"tags": [
"private"
],
-
"description": "Delete a given participation key by id",
"produces": [
"application/json"
],
-
"schemes": [
"http"
],
@@ -624,15 +611,11 @@
"description": "Unknown Error"
}
}
-
},
-
"get": {
-
"tags": [
"private"
],
-
"description": "Given a participation id, return information about that participation key",
"produces": [
"application/json"
@@ -2590,16 +2573,7 @@
"ParticipationKeyResponse": {
"description": "A detailed description of a participation id",
"schema": {
- "type": "object",
- "required": [
- "participationKey"
- ],
- "properties": {
- "participationKey": {
- "description": "Detailed description of a participation key",
- "type": "string"
- }
- }
+ "$ref": "#/definitions/ParticipationKey"
}
},
"DeleteParticipationIdResponse" : {
diff --git a/daemon/algod/api/algod.oas3.yml b/daemon/algod/api/algod.oas3.yml
index a8a4d9ea8b..b302823104 100644
--- a/daemon/algod/api/algod.oas3.yml
+++ b/daemon/algod/api/algod.oas3.yml
@@ -461,16 +461,7 @@
"content": {
"application/json": {
"schema": {
- "properties": {
- "participationKey": {
- "description": "Detailed description of a participation key",
- "type": "string"
- }
- },
- "required": [
- "participationKey"
- ],
- "type": "object"
+ "$ref": "#/components/schemas/ParticipationKey"
}
}
},
@@ -2771,16 +2762,7 @@
"content": {
"application/json": {
"schema": {
- "properties": {
- "participationKey": {
- "description": "Detailed description of a participation key",
- "type": "string"
- }
- },
- "required": [
- "participationKey"
- ],
- "type": "object"
+ "$ref": "#/components/schemas/ParticipationKey"
}
}
},
diff --git a/daemon/algod/api/server/v2/generated/private/routes.go b/daemon/algod/api/server/v2/generated/private/routes.go
index a061b310cf..e294ea1e75 100644
--- a/daemon/algod/api/server/v2/generated/private/routes.go
+++ b/daemon/algod/api/server/v2/generated/private/routes.go
@@ -277,142 +277,142 @@ func RegisterHandlers(router interface {
// Base64 encoded, gzipped, json marshaled Swagger object
var swaggerSpec = []string{
- "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",
+ "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",
}
// GetSwagger returns the Swagger specification corresponding to the generated code
diff --git a/daemon/algod/api/server/v2/generated/private/types.go b/daemon/algod/api/server/v2/generated/private/types.go
index e8ba9221e9..78137ac3ea 100644
--- a/daemon/algod/api/server/v2/generated/private/types.go
+++ b/daemon/algod/api/server/v2/generated/private/types.go
@@ -617,11 +617,7 @@ type NodeStatusResponse struct {
}
// ParticipationKeyResponse defines model for ParticipationKeyResponse.
-type ParticipationKeyResponse struct {
-
- // Detailed description of a participation key
- ParticipationKey string `json:"participationKey"`
-}
+type ParticipationKeyResponse ParticipationKey
// ParticipationKeysResponse defines model for ParticipationKeysResponse.
type ParticipationKeysResponse []ParticipationKey
diff --git a/daemon/algod/api/server/v2/generated/routes.go b/daemon/algod/api/server/v2/generated/routes.go
index 80a74df416..2962a328a6 100644
--- a/daemon/algod/api/server/v2/generated/routes.go
+++ b/daemon/algod/api/server/v2/generated/routes.go
@@ -617,178 +617,178 @@ func RegisterHandlers(router interface {
var swaggerSpec = []string{
"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",
- "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",
+ "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",
+ "HcEdLqVk55g78cPl/wsAAP//K7GmSonxAAA=",
}
// GetSwagger returns the Swagger specification corresponding to the generated code
diff --git a/daemon/algod/api/server/v2/generated/types.go b/daemon/algod/api/server/v2/generated/types.go
index 6e10c1f780..b9066ba0ec 100644
--- a/daemon/algod/api/server/v2/generated/types.go
+++ b/daemon/algod/api/server/v2/generated/types.go
@@ -617,11 +617,7 @@ type NodeStatusResponse struct {
}
// ParticipationKeyResponse defines model for ParticipationKeyResponse.
-type ParticipationKeyResponse struct {
-
- // Detailed description of a participation key
- ParticipationKey string `json:"participationKey"`
-}
+type ParticipationKeyResponse ParticipationKey
// ParticipationKeysResponse defines model for ParticipationKeysResponse.
type ParticipationKeysResponse []ParticipationKey
diff --git a/data/abi/abi_encode.go b/data/abi/abi_encode.go
index fa5dbd57c8..c2b7a24804 100644
--- a/data/abi/abi_encode.go
+++ b/data/abi/abi_encode.go
@@ -481,37 +481,39 @@ func decodeTuple(encoded []byte, childT []Type) ([]interface{}, error) {
// ParseArgJSONtoByteSlice convert input method arguments to ABI encoded bytes
// it converts funcArgTypes into a tuple type and apply changes over input argument string (in JSON format)
// if there are greater or equal to 15 inputs, then we compact the tailing inputs into one tuple
-func ParseArgJSONtoByteSlice(funcArgTypes string, jsonArgs []string, applicationArgs *[][]byte) error {
- abiTupleT, err := TypeOf(funcArgTypes)
- if err != nil {
- return err
+func ParseArgJSONtoByteSlice(argTypes []string, jsonArgs []string, applicationArgs *[][]byte) error {
+ abiTypes := make([]Type, len(argTypes))
+ for i, typeString := range argTypes {
+ abiType, err := TypeOf(typeString)
+ if err != nil {
+ return err
+ }
+ abiTypes[i] = abiType
}
- if len(abiTupleT.childTypes) != len(jsonArgs) {
- return fmt.Errorf("input argument number %d != method argument number %d", len(jsonArgs), len(abiTupleT.childTypes))
+
+ if len(abiTypes) != len(jsonArgs) {
+ return fmt.Errorf("input argument number %d != method argument number %d", len(jsonArgs), len(abiTypes))
}
// change the input args to be 1 - 14 + 15 (compacting everything together)
if len(jsonArgs) > 14 {
- compactedType, err := MakeTupleType(abiTupleT.childTypes[14:])
+ compactedType, err := MakeTupleType(abiTypes[14:])
if err != nil {
return err
}
- abiTupleT.childTypes = abiTupleT.childTypes[:14]
- abiTupleT.childTypes = append(abiTupleT.childTypes, compactedType)
- abiTupleT.staticLength = 15
+ abiTypes = append(abiTypes[:14], compactedType)
remainingJSON := "[" + strings.Join(jsonArgs[14:], ",") + "]"
- jsonArgs = jsonArgs[:14]
- jsonArgs = append(jsonArgs, remainingJSON)
+ jsonArgs = append(jsonArgs[:14], remainingJSON)
}
// parse JSON value to ABI encoded bytes
for i := 0; i < len(jsonArgs); i++ {
- interfaceVal, err := abiTupleT.childTypes[i].UnmarshalFromJSON([]byte(jsonArgs[i]))
+ interfaceVal, err := abiTypes[i].UnmarshalFromJSON([]byte(jsonArgs[i]))
if err != nil {
return err
}
- abiEncoded, err := abiTupleT.childTypes[i].Encode(interfaceVal)
+ abiEncoded, err := abiTypes[i].Encode(interfaceVal)
if err != nil {
return err
}
@@ -520,30 +522,40 @@ func ParseArgJSONtoByteSlice(funcArgTypes string, jsonArgs []string, application
return nil
}
-// ParseMethodSignature parses a method of format `method(...argTypes...)retType`
-// into `(...argTypes)` and `retType`
-func ParseMethodSignature(methodSig string) (string, string, error) {
- var stack []int
-
- for index, chr := range methodSig {
- if chr == '(' {
- stack = append(stack, index)
- } else if chr == ')' {
- if len(stack) == 0 {
- break
+// ParseMethodSignature parses a method of format `method(argType1,argType2,...)retType`
+// into `method` {`argType1`,`argType2`,..} and `retType`
+func ParseMethodSignature(methodSig string) (name string, argTypes []string, returnType string, err error) {
+ argsStart := strings.Index(methodSig, "(")
+ if argsStart == -1 {
+ err = fmt.Errorf("Invalid method signature: %s", methodSig)
+ return
+ }
+
+ argsEnd := -1
+ depth := 0
+ for index, char := range methodSig {
+ if char == '(' {
+ depth++
+ } else if char == ')' {
+ if depth == 0 {
+ err = fmt.Errorf("Unpaired parenthesis in method signature: %s", methodSig)
+ return
}
- leftParenIndex := stack[len(stack)-1]
- stack = stack[:len(stack)-1]
- if len(stack) == 0 {
- returnType := methodSig[index+1:]
- if _, err := TypeOf(returnType); err != nil {
- if returnType != "void" {
- return "", "", fmt.Errorf("cannot infer return type: %s", returnType)
- }
- }
- return methodSig[leftParenIndex : index+1], methodSig[index+1:], nil
+ depth--
+ if depth == 0 {
+ argsEnd = index
+ break
}
}
}
- return "", "", fmt.Errorf("unpaired parentheses: %s", methodSig)
+
+ if argsEnd == -1 {
+ err = fmt.Errorf("Invalid method signature: %s", methodSig)
+ return
+ }
+
+ name = methodSig[:argsStart]
+ argTypes, err = parseTupleContent(methodSig[argsStart+1 : argsEnd])
+ returnType = methodSig[argsEnd+1:]
+ return
}
diff --git a/data/abi/abi_encode_test.go b/data/abi/abi_encode_test.go
index c585564c61..a86fe46b95 100644
--- a/data/abi/abi_encode_test.go
+++ b/data/abi/abi_encode_test.go
@@ -1001,3 +1001,55 @@ func TestRandomABIEncodeDecodeRoundTrip(t *testing.T) {
addTupleRandomValues(t, Tuple, &testValuePool)
categorySelfRoundTripTest(t, testValuePool[Tuple])
}
+
+func TestParseMethodSignature(t *testing.T) {
+ partitiontest.PartitionTest(t)
+
+ tests := []struct {
+ signature string
+ name string
+ argTypes []string
+ returnType string
+ }{
+ {
+ signature: "add(uint8,uint16,pay,account,txn)uint32",
+ name: "add",
+ argTypes: []string{"uint8", "uint16", "pay", "account", "txn"},
+ returnType: "uint32",
+ },
+ {
+ signature: "nothing()void",
+ name: "nothing",
+ argTypes: []string{},
+ returnType: "void",
+ },
+ {
+ signature: "tupleArgs((uint8,uint128),account,(string,(bool,bool)))bool",
+ name: "tupleArgs",
+ argTypes: []string{"(uint8,uint128)", "account", "(string,(bool,bool))"},
+ returnType: "bool",
+ },
+ {
+ signature: "tupleReturn(uint64)(bool,bool,bool)",
+ name: "tupleReturn",
+ argTypes: []string{"uint64"},
+ returnType: "(bool,bool,bool)",
+ },
+ {
+ signature: "tupleArgsAndReturn((uint8,uint128),account,(string,(bool,bool)))(bool,bool,bool)",
+ name: "tupleArgsAndReturn",
+ argTypes: []string{"(uint8,uint128)", "account", "(string,(bool,bool))"},
+ returnType: "(bool,bool,bool)",
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.signature, func(t *testing.T) {
+ name, argTypes, returnType, err := ParseMethodSignature(test.signature)
+ require.NoError(t, err)
+ require.Equal(t, test.name, name)
+ require.Equal(t, test.argTypes, argTypes)
+ require.Equal(t, test.returnType, returnType)
+ })
+ }
+}
diff --git a/data/abi/abi_type.go b/data/abi/abi_type.go
index eb93f9eea1..5a71ae3cc6 100644
--- a/data/abi/abi_type.go
+++ b/data/abi/abi_type.go
@@ -457,3 +457,37 @@ func (t Type) ByteLen() (int, error) {
return -1, fmt.Errorf("%s is a dynamic type", t.String())
}
}
+
+// AnyTransactionType is the ABI argument type string for a nonspecific transaction argument
+const AnyTransactionType = "txn"
+
+// IsTransactionType checks if a type string represents a transaction type
+// argument, such as "txn", "pay", "keyreg", etc.
+func IsTransactionType(s string) bool {
+ switch s {
+ case AnyTransactionType, "pay", "keyreg", "acfg", "axfer", "afrz", "appl":
+ return true
+ default:
+ return false
+ }
+}
+
+// AccountReferenceType is the ABI argument type string for account references
+const AccountReferenceType = "account"
+
+// AssetReferenceType is the ABI argument type string for asset references
+const AssetReferenceType = "asset"
+
+// ApplicationReferenceType is the ABI argument type string for application references
+const ApplicationReferenceType = "application"
+
+// IsReferenceType checks if a type string represents a reference type argument,
+// such as "account", "asset", or "application".
+func IsReferenceType(s string) bool {
+ switch s {
+ case AccountReferenceType, AssetReferenceType, ApplicationReferenceType:
+ return true
+ default:
+ return false
+ }
+}
diff --git a/data/account/participationRegistry.go b/data/account/participationRegistry.go
index 213e2be505..a4aa0629af 100644
--- a/data/account/participationRegistry.go
+++ b/data/account/participationRegistry.go
@@ -82,6 +82,22 @@ type ParticipationRecord struct {
Voting *crypto.OneTimeSignatureSecrets
}
+// StateProofKey is a placeholder for the real state proof key type.
+// PKI TODO: Replace this with a real object.
+type StateProofKey []byte
+
+// ParticipationRecordForRound adds in the per-round state proof key.
+type ParticipationRecordForRound struct {
+ ParticipationRecord
+
+ StateProof StateProofKey
+}
+
+// IsZero returns true if the object contains zero values.
+func (r ParticipationRecordForRound) IsZero() bool {
+ return r.StateProof == nil && r.ParticipationRecord.IsZero()
+}
+
var zeroParticipationRecord = ParticipationRecord{}
// IsZero returns true if the object contains zero values.
@@ -152,11 +168,18 @@ var ErrMultipleKeysForID = errors.New("multiple valid keys found for the same pa
// ErrNoKeyForID there may be cases where a key is deleted and used at the same time, so this error should be handled.
var ErrNoKeyForID = errors.New("no valid key found for the participationID")
+// ErrSecretNotFound is used when attempting to lookup secrets for a particular round.
+var ErrSecretNotFound = errors.New("the participation ID did not have secrets for the requested round")
+
// ParticipationRegistry contain all functions for interacting with the Participation Registry.
type ParticipationRegistry interface {
// Insert adds a record to storage and computes the ParticipationID
Insert(record Participation) (ParticipationID, error)
+ // AppendKeys appends state proof keys to an existing Participation record. Keys can only be appended
+ // once, an error will occur when the data is flushed when inserting a duplicate key.
+ AppendKeys(id ParticipationID, keys map[uint64]StateProofKey) error
+
// Delete removes a record from storage.
Delete(id ParticipationID) error
@@ -169,6 +192,9 @@ type ParticipationRegistry interface {
// GetAll of the participation records.
GetAll() []ParticipationRecord
+ // GetForRound fetches a record with all secrets for a particular round.
+ GetForRound(id ParticipationID, round basics.Round) (ParticipationRecordForRound, error)
+
// Register updates the EffectiveFirst and EffectiveLast fields. If there are multiple records for the account
// then it is possible for multiple records to be updated.
Register(id ParticipationID, on basics.Round) error
@@ -256,8 +282,9 @@ const (
key BLOB NOT NULL, --* msgpack encoding of ParticipationAccount.BlockProof.SignatureAlgorithm
PRIMARY KEY (pk, round)
)`
- insertKeysetQuery = `INSERT INTO Keysets (participationID, account, firstValidRound, lastValidRound, keyDilution, vrf) VALUES (?, ?, ?, ?, ?, ?)`
- insertRollingQuery = `INSERT INTO Rolling (pk, voting) VALUES (?, ?)`
+ insertKeysetQuery = `INSERT INTO Keysets (participationID, account, firstValidRound, lastValidRound, keyDilution, vrf, stateProof) VALUES (?, ?, ?, ?, ?, ?, ?)`
+ insertRollingQuery = `INSERT INTO Rolling (pk, voting) VALUES (?, ?)`
+ appendStateProofKeysQuery = `INSERT INTO StateProofKeys (pk, round, key) VALUES(?, ?, ?)`
// SELECT pk FROM Keysets WHERE participationID = ?
selectPK = `SELECT pk FROM Keysets WHERE participationID = ? LIMIT 1`
@@ -270,6 +297,10 @@ const (
FROM Keysets k
INNER JOIN Rolling r
ON k.pk = r.pk`
+ selectStateProofKeys = `SELECT s.key
+ FROM StateProofKeys s
+ WHERE round=?
+ AND pk IN (SELECT pk FROM Keysets WHERE participationID=?)`
deleteKeysets = `DELETE FROM Keysets WHERE pk=?`
deleteRolling = `DELETE FROM Rolling WHERE pk=?`
updateRollingFieldsSQL = `UPDATE Rolling
@@ -332,6 +363,7 @@ type updatingParticipationRecord struct {
type partDBWriteRecord struct {
insertID ParticipationID
insert Participation
+ keys map[uint64]StateProofKey
registerUpdated map[ParticipationID]updatingParticipationRecord
@@ -380,7 +412,11 @@ func (db *participationDB) writeThread() {
if len(wr.registerUpdated) != 0 {
err = db.registerInner(wr.registerUpdated)
} else if !wr.insertID.IsZero() {
- err = db.insertInner(wr.insert, wr.insertID)
+ if wr.insert != (Participation{}) {
+ err = db.insertInner(wr.insert, wr.insertID)
+ } else if len(wr.keys) != 0 {
+ err = db.appendKeysInner(wr.insertID, wr.keys)
+ }
} else if !wr.delete.IsZero() {
err = db.deleteInner(wr.delete)
} else if wr.flushResultChannel != nil {
@@ -413,9 +449,9 @@ func verifyExecWithOneRowEffected(err error, result sql.Result, operationName st
}
func (db *participationDB) insertInner(record Participation, id ParticipationID) (err error) {
-
var rawVRF []byte
var rawVoting []byte
+ var rawStateProof []byte
if record.VRF != nil {
rawVRF = protocol.Encode(record.VRF)
@@ -424,6 +460,7 @@ func (db *participationDB) insertInner(record Participation, id ParticipationID)
voting := record.Voting.Snapshot()
rawVoting = protocol.Encode(&voting)
}
+ // PKI TODO: Extract state proof from record.
err = db.store.Wdb.Atomic(func(ctx context.Context, tx *sql.Tx) error {
result, err := tx.Exec(
@@ -433,8 +470,9 @@ func (db *participationDB) insertInner(record Participation, id ParticipationID)
record.FirstValid,
record.LastValid,
record.KeyDilution,
- rawVRF)
- if err := verifyExecWithOneRowEffected(err, result, "insert keyset"); err != nil {
+ rawVRF,
+ rawStateProof)
+ if err = verifyExecWithOneRowEffected(err, result, "insert keyset"); err != nil {
return err
}
pk, err := result.LastInsertId()
@@ -444,7 +482,7 @@ func (db *participationDB) insertInner(record Participation, id ParticipationID)
// Create Rolling entry
result, err = tx.Exec(insertRollingQuery, pk, rawVoting)
- if err := verifyExecWithOneRowEffected(err, result, "insert rolling"); err != nil {
+ if err = verifyExecWithOneRowEffected(err, result, "insert rolling"); err != nil {
return err
}
@@ -453,6 +491,37 @@ func (db *participationDB) insertInner(record Participation, id ParticipationID)
return err
}
+func (db *participationDB) appendKeysInner(id ParticipationID, keys map[uint64]StateProofKey) error {
+ err := db.store.Wdb.Atomic(func(ctx context.Context, tx *sql.Tx) error {
+ // Fetch primary key
+ var pk int
+ row := tx.QueryRow(selectPK, id[:])
+ err := row.Scan(&pk)
+ if err == sql.ErrNoRows {
+ // nothing to do.
+ return nil
+ }
+ if err != nil {
+ return fmt.Errorf("unable to scan pk: %w", err)
+ }
+
+ stmt, err := tx.Prepare(appendStateProofKeysQuery)
+ if err != nil {
+ return fmt.Errorf("unable to prepare state proof insert: %w", err)
+ }
+
+ for k, v := range keys {
+ result, err := stmt.Exec(pk, k, v)
+ if err = verifyExecWithOneRowEffected(err, result, "append keys"); err != nil {
+ return err
+ }
+ }
+
+ return nil
+ })
+ return err
+}
+
func (db *participationDB) registerInner(updated map[ParticipationID]updatingParticipationRecord) error {
var cacheDeletes []ParticipationID
err := db.store.Wdb.Atomic(func(ctx context.Context, tx *sql.Tx) error {
@@ -502,12 +571,12 @@ func (db *participationDB) deleteInner(id ParticipationID) error {
// Delete rows
result, err := tx.Exec(deleteKeysets, pk)
- if err := verifyExecWithOneRowEffected(err, result, "delete keyset"); err != nil {
+ if err = verifyExecWithOneRowEffected(err, result, "delete keyset"); err != nil {
return err
}
result, err = tx.Exec(deleteRolling, pk)
- if err := verifyExecWithOneRowEffected(err, result, "delete rolling"); err != nil {
+ if err = verifyExecWithOneRowEffected(err, result, "delete rolling"); err != nil {
return err
}
@@ -578,6 +647,8 @@ func (db *participationDB) Insert(record Participation) (id ParticipationID, err
id = record.ID()
if _, ok := db.cache[id]; ok {
+ // PKI TODO: Add a special case to set the StateProof public key if it is in the input
+ // but not in the cache.
return id, ErrAlreadyInserted
}
@@ -619,6 +690,27 @@ func (db *participationDB) Insert(record Participation) (id ParticipationID, err
return
}
+func (db *participationDB) AppendKeys(id ParticipationID, keys map[uint64]StateProofKey) error {
+ db.mutex.Lock()
+ defer db.mutex.Unlock()
+
+ if _, ok := db.cache[id]; !ok {
+ return ErrParticipationIDNotFound
+ }
+
+ keyCopy := make(map[uint64]StateProofKey, len(keys))
+ for k, v := range keys {
+ keyCopy[k] = v // PKI TODO: Deep copy?
+ }
+
+ // Update the DB asynchronously.
+ db.writeQueue <- partDBWriteRecord{
+ insertID: id,
+ keys: keyCopy,
+ }
+ return nil
+}
+
func (db *participationDB) Delete(id ParticipationID) error {
db.mutex.Lock()
defer db.mutex.Unlock()
@@ -629,6 +721,7 @@ func (db *participationDB) Delete(id ParticipationID) error {
}
delete(db.dirty, id)
delete(db.cache, id)
+
// do the db part async
db.writeQueue <- partDBWriteRecord{
delete: id,
@@ -770,6 +863,34 @@ func (db *participationDB) GetAll() []ParticipationRecord {
return results
}
+// GetForRound fetches a record with all secrets for a particular round.
+func (db *participationDB) GetForRound(id ParticipationID, round basics.Round) (ParticipationRecordForRound, error) {
+ var result ParticipationRecordForRound
+ result.ParticipationRecord = db.Get(id)
+ if result.ParticipationRecord.IsZero() {
+ return ParticipationRecordForRound{}, ErrParticipationIDNotFound
+ }
+
+ err := db.store.Rdb.Atomic(func(ctx context.Context, tx *sql.Tx) error {
+ row := tx.QueryRow(selectStateProofKeys, round, id[:])
+ err := row.Scan(&result.StateProof)
+ if err == sql.ErrNoRows {
+ return ErrSecretNotFound
+ }
+ if err != nil {
+ return fmt.Errorf("error while querying secrets: %w", err)
+ }
+
+ return nil
+ })
+
+ if err != nil {
+ return ParticipationRecordForRound{}, fmt.Errorf("unable to lookup secrets: %w", err)
+ }
+
+ return result, nil
+}
+
// updateRollingFields sets all of the rolling fields according to the record object.
func updateRollingFields(ctx context.Context, tx *sql.Tx, record ParticipationRecord) error {
result, err := tx.ExecContext(ctx, updateRollingFieldsSQL,
diff --git a/data/account/participationRegistryBench_test.go b/data/account/participationRegistryBench_test.go
new file mode 100644
index 0000000000..d1a97d400b
--- /dev/null
+++ b/data/account/participationRegistryBench_test.go
@@ -0,0 +1,80 @@
+// Copyright (C) 2019-2021 Algorand, Inc.
+// This file is part of go-algorand
+//
+// go-algorand is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// go-algorand is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with go-algorand. If not, see .
+
+package account
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/algorand/go-algorand/data/basics"
+ "github.com/algorand/go-algorand/logging"
+ "github.com/algorand/go-algorand/util/db"
+)
+
+func benchmarkKeyRegistration(numKeys int, b *testing.B) {
+ // setup
+ rootDB, err := db.OpenPair(b.Name(), true)
+ if err != nil {
+ b.Fail()
+ }
+ registry, err := makeParticipationRegistry(rootDB, logging.TestingLog(b))
+ if err != nil {
+ b.Fail()
+ }
+
+ // Insert records so that we can t
+ b.Run(fmt.Sprintf("KeyInsert_%d", numKeys), func(b *testing.B) {
+ for n := 0; n < b.N; n++ {
+ for key := 0; key < numKeys; key++ {
+ p := makeTestParticipation(key, basics.Round(0), basics.Round(1000000), 3)
+ registry.Insert(p)
+ }
+ }
+ })
+
+ // The first call to Register updates the DB.
+ b.Run(fmt.Sprintf("KeyRegistered_%d", numKeys), func(b *testing.B) {
+ for n := 0; n < b.N; n++ {
+ for key := 0; key < numKeys; key++ {
+ p := makeTestParticipation(key, basics.Round(0), basics.Round(1000000), 3)
+
+ // Unfortunately we need to repeatedly clear out the registration fields to ensure the
+ // db update runs each time this is called.
+ record := registry.cache[p.ID()]
+ record.EffectiveFirst = 0
+ record.EffectiveLast = 0
+ registry.cache[p.ID()] = record
+ registry.Register(p.ID(), 50)
+ }
+ }
+ })
+
+ // The keys should now be updated, so Register is a no-op.
+ b.Run(fmt.Sprintf("NoOp_%d", numKeys), func(b *testing.B) {
+ for n := 0; n < b.N; n++ {
+ for key := 0; key < numKeys; key++ {
+ p := makeTestParticipation(key, basics.Round(0), basics.Round(1000000), 3)
+ registry.Register(p.ID(), 50)
+ }
+ }
+ })
+}
+
+func BenchmarkKeyRegistration1(b *testing.B) { benchmarkKeyRegistration(1, b) }
+func BenchmarkKeyRegistration5(b *testing.B) { benchmarkKeyRegistration(5, b) }
+func BenchmarkKeyRegistration10(b *testing.B) { benchmarkKeyRegistration(10, b) }
+func BenchmarkKeyRegistration50(b *testing.B) { benchmarkKeyRegistration(50, b) }
diff --git a/data/account/participationRegistry_test.go b/data/account/participationRegistry_test.go
index d000f16cbb..e960317e73 100644
--- a/data/account/participationRegistry_test.go
+++ b/data/account/participationRegistry_test.go
@@ -433,6 +433,7 @@ func TestParticipation_RecordMultipleUpdates_DB(t *testing.T) {
record.FirstValid,
record.LastValid,
record.KeyDilution,
+ nil,
nil)
if err != nil {
return fmt.Errorf("unable to insert keyset: %w", err)
@@ -714,56 +715,93 @@ func TestFlushDeadlock(t *testing.T) {
wg.Wait()
}
-func benchmarkKeyRegistration(numKeys int, b *testing.B) {
- // setup
- rootDB, err := db.OpenPair(b.Name(), true)
- if err != nil {
- b.Fail()
+func TestAddStateProofKeys(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ a := assert.New(t)
+ registry := getRegistry(t)
+ defer registryCloseTest(t, registry)
+
+ // Install a key to add StateProof keys.
+ max := uint64(1000)
+ p := makeTestParticipation(1, 0, basics.Round(max), 3)
+ id, err := registry.Insert(p)
+ a.NoError(err)
+ a.Equal(p.ID(), id)
+
+ // Wait for async DB operations to finish.
+ err = registry.Flush(10 * time.Second)
+ a.NoError(err)
+
+ // Initialize keys array.
+ keys := make(map[uint64]StateProofKey)
+ for i := uint64(0); i <= max; i++ {
+ bs := make([]byte, 8)
+ binary.LittleEndian.PutUint64(bs, i)
+ keys[i] = bs
}
- registry, err := makeParticipationRegistry(rootDB, logging.TestingLog(b))
- if err != nil {
- b.Fail()
+
+ err = registry.AppendKeys(id, keys)
+ a.NoError(err)
+
+ // Wait for async DB operations to finish.
+ err = registry.Flush(10 * time.Second)
+ a.NoError(err)
+
+ // Make sure we're able to fetch the same data that was put in.
+ for i := uint64(0); i <= max; i++ {
+ r, err := registry.GetForRound(id, basics.Round(i))
+ a.NoError(err)
+ a.Equal(keys[i], r.StateProof)
+ number := binary.LittleEndian.Uint64(r.StateProof)
+ a.Equal(i, number)
}
+}
- // Insert records so that we can t
- b.Run(fmt.Sprintf("KeyInsert_%d", numKeys), func(b *testing.B) {
- for n := 0; n < b.N; n++ {
- for key := 0; key < numKeys; key++ {
- p := makeTestParticipation(key, basics.Round(0), basics.Round(1000000), 3)
- registry.Insert(p)
- }
- }
- })
+func TestSecretNotFound(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ a := assert.New(t)
+ registry := getRegistry(t)
+ defer registryCloseTest(t, registry)
- // The first call to Register updates the DB.
- b.Run(fmt.Sprintf("KeyRegistered_%d", numKeys), func(b *testing.B) {
- for n := 0; n < b.N; n++ {
- for key := 0; key < numKeys; key++ {
- p := makeTestParticipation(key, basics.Round(0), basics.Round(1000000), 3)
-
- // Unfortunately we need to repeatedly clear out the registration fields to ensure the
- // db update runs each time this is called.
- record := registry.cache[p.ID()]
- record.EffectiveFirst = 0
- record.EffectiveLast = 0
- registry.cache[p.ID()] = record
- registry.Register(p.ID(), 50)
- }
- }
- })
+ // Install a key for testing
+ p := makeTestParticipation(1, 0, 2, 3)
+ id, err := registry.Insert(p)
+ a.NoError(err)
+ a.Equal(p.ID(), id)
- // The keys should now be updated, so Register is a no-op.
- b.Run(fmt.Sprintf("NoOp_%d", numKeys), func(b *testing.B) {
- for n := 0; n < b.N; n++ {
- for key := 0; key < numKeys; key++ {
- p := makeTestParticipation(key, basics.Round(0), basics.Round(1000000), 3)
- registry.Register(p.ID(), 50)
- }
- }
- })
+ r, err := registry.GetForRound(id, basics.Round(100))
+
+ a.True(r.IsZero())
+ a.Error(err)
+ a.ErrorIs(err, ErrSecretNotFound)
}
-func BenchmarkKeyRegistration1(b *testing.B) { benchmarkKeyRegistration(1, b) }
-func BenchmarkKeyRegistration5(b *testing.B) { benchmarkKeyRegistration(5, b) }
-func BenchmarkKeyRegistration10(b *testing.B) { benchmarkKeyRegistration(10, b) }
-func BenchmarkKeyRegistration50(b *testing.B) { benchmarkKeyRegistration(50, b) }
+func TestAddingSecretTwice(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ a := assert.New(t)
+ registry := getRegistry(t)
+ defer registryCloseTest(t, registry)
+
+ // Install a key for testing
+ p := makeTestParticipation(1, 0, 2, 3)
+ id, err := registry.Insert(p)
+ a.NoError(err)
+ a.Equal(p.ID(), id)
+
+ // Append key
+ keys := make(map[uint64]StateProofKey)
+ bs := make([]byte, 8)
+ binary.LittleEndian.PutUint64(bs, 10)
+ keys[0] = bs
+
+ err = registry.AppendKeys(id, keys)
+ a.NoError(err)
+
+ // The error doesn't happen until the data persists.
+ err = registry.AppendKeys(id, keys)
+ a.NoError(err)
+
+ err = registry.Flush(10 * time.Second)
+ a.Error(err)
+ a.EqualError(err, "unable to execute append keys: UNIQUE constraint failed: StateProofKeys.pk, StateProofKeys.round")
+}
diff --git a/data/transactions/logic/README.md b/data/transactions/logic/README.md
index 6a08bb8335..25445ec3d8 100644
--- a/data/transactions/logic/README.md
+++ b/data/transactions/logic/README.md
@@ -165,6 +165,7 @@ various sizes.
| `extract_uint16` | pop a byte-array A and integer B. Extract a range of bytes from A starting at B up to but not including B+2, convert bytes as big endian and push the uint64 result. If B+2 is larger than the array length, the program fails |
| `extract_uint32` | pop a byte-array A and integer B. Extract a range of bytes from A starting at B up to but not including B+4, convert bytes as big endian and push the uint64 result. If B+4 is larger than the array length, the program fails |
| `extract_uint64` | pop a byte-array A and integer B. Extract a range of bytes from A starting at B up to but not including B+8, convert bytes as big endian and push the uint64 result. If B+8 is larger than the array length, the program fails |
+| `base64_decode e` | decode X which was base64-encoded using _encoding alphabet_ E. Fail if X is not base64 encoded with alphabet E |
These opcodes take byte-array values that are interpreted as
big-endian unsigned integers. For mathematical operators, the
diff --git a/data/transactions/logic/TEAL_opcodes.md b/data/transactions/logic/TEAL_opcodes.md
index d38645afb3..f93ae2feb9 100644
--- a/data/transactions/logic/TEAL_opcodes.md
+++ b/data/transactions/logic/TEAL_opcodes.md
@@ -857,6 +857,17 @@ When A is a uint64, index 0 is the least significant bit. Setting bit 3 to 1 on
- pop a byte-array A and integer B. Extract a range of bytes from A starting at B up to but not including B+8, convert bytes as big endian and push the uint64 result. If B+8 is larger than the array length, the program fails
- LogicSigVersion >= 5
+## base64_decode e
+
+- Opcode: 0x5c {uint8 alphabet index}
+- Pops: *... stack*, []byte
+- Pushes: []byte
+- decode X which was base64-encoded using _encoding alphabet_ E. Fail if X is not base64 encoded with alphabet E
+- **Cost**: 25
+- LogicSigVersion >= 6
+
+decodes X using the base64 encoding alphabet E. Specify the alphabet with an immediate arg either as URL and Filename Safe (`URLAlph`) or Standard (`StdAlph`). See RFC 4648 (sections 4 and 5)
+
## balance
- Opcode: 0x60
diff --git a/data/transactions/logic/assembler.go b/data/transactions/logic/assembler.go
index 34f9bcecf8..403c357b8b 100644
--- a/data/transactions/logic/assembler.go
+++ b/data/transactions/logic/assembler.go
@@ -1224,6 +1224,28 @@ func assembleEcdsa(ops *OpStream, spec *OpSpec, args []string) error {
return nil
}
+func assembleBase64Decode(ops *OpStream, spec *OpSpec, args []string) error {
+ if len(args) != 1 {
+ return ops.errorf("%s expects one argument", spec.Name)
+ }
+
+ alph, ok := base64AlphabetSpecByName[args[0]]
+ if !ok {
+ return ops.errorf("%s unknown alphabet: %#v", spec.Name, args[0])
+ }
+ if alph.version > ops.Version {
+ //nolint:errcheck // we continue to maintain typestack
+ ops.errorf("%s %s available in version %d. Missed #pragma version?", spec.Name, args[0], alph.version)
+ }
+
+ val := alph.field
+ ops.pending.WriteByte(spec.Opcode)
+ ops.pending.WriteByte(uint8(val))
+ ops.trace("%s (%s)", alph.field, alph.ftype)
+ ops.returns(alph.ftype)
+ return nil
+}
+
type assembleFunc func(*OpStream, *OpSpec, []string) error
// Basic assembly. Any extra bytes of opcode are encoded as byte immediates.
@@ -2646,6 +2668,20 @@ func disEcdsa(dis *disassembleState, spec *OpSpec) (string, error) {
return fmt.Sprintf("%s %s", spec.Name, EcdsaCurveNames[arg]), nil
}
+func disBase64Decode(dis *disassembleState, spec *OpSpec) (string, error) {
+ lastIdx := dis.pc + 1
+ if len(dis.program) <= lastIdx {
+ missing := lastIdx - len(dis.program) + 1
+ return "", fmt.Errorf("unexpected %s opcode end: missing %d bytes", spec.Name, missing)
+ }
+ dis.nextpc = dis.pc + 2
+ b64dArg := dis.program[dis.pc+1]
+ if int(b64dArg) >= len(base64AlphabetNames) {
+ return "", fmt.Errorf("invalid base64_decode arg index %d at pc=%d", b64dArg, dis.pc)
+ }
+ return fmt.Sprintf("%s %s", spec.Name, base64AlphabetNames[b64dArg]), nil
+}
+
type disInfo struct {
pcOffset []PCOffset
hasStatefulOps bool
diff --git a/data/transactions/logic/assembler_test.go b/data/transactions/logic/assembler_test.go
index b360991938..d297b0edc7 100644
--- a/data/transactions/logic/assembler_test.go
+++ b/data/transactions/logic/assembler_test.go
@@ -346,6 +346,7 @@ const v6Nonsense = v5Nonsense + `
itxn_next
gitxn 4 CreatedAssetID
gitxna 3 Logs 12
+base64_decode URLAlph
int 0
dup
gloadss
@@ -366,7 +367,7 @@ var compiled = map[uint64]string{
3: "032008b7a60cf8acd19181cf959a12f8acd19181cf951af8acd19181cf15f8acd191810f01020026050212340c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d024242047465737400320032013202320328292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e0102222324252104082209240a220b230c240d250e230f23102311231223132314181b1c2b171615400003290349483403350222231d4a484848482a50512a63222352410003420000432105602105612105270463484821052b62482b642b65484821052b2106662b21056721072b682b692107210570004848210771004848361c0037001a0031183119311b311d311e311f3120210721051e312131223123312431253126312731283129312a312b312c312d312e312f4478222105531421055427042106552105082106564c4d4b02210538212106391c0081e80780046a6f686e",
4: "042004010200b7a60c26040242420c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f23102311231223132314181b1c28171615400003290349483403350222231d4a484848482a50512a632223524100034200004322602261222b634848222862482864286548482228236628226724286828692422700048482471004848361c0037001a0031183119311b311d311e311f312024221e312131223123312431253126312731283129312a312b312c312d312e312f44782522531422542b2355220823564c4d4b0222382123391c0081e80780046a6f686e2281d00f24231f880003420001892223902291922394239593a0a1a2a3a4a5a6a7a8a9aaabacadae23af3a00003b003c003d8164",
5: "052004010002b7a60c26050242420c68656c6c6f20776f726c6421070123456789abcd208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292b0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f23102311231223132314181b1c28171615400003290349483403350222231d4a484848482b50512a632223524100034200004322602261222704634848222862482864286548482228246628226723286828692322700048482371004848361c0037001a0031183119311b311d311e311f312023221e312131223123312431253126312731283129312a312b312c312d312e312f447825225314225427042455220824564c4d4b0222382124391c0081e80780046a6f686e2281d00f23241f880003420001892224902291922494249593a0a1a2a3a4a5a6a7a8a9aaabacadae24af3a00003b003c003d816472064e014f012a57000823810858235b235a2359b03139330039b1b200b322c01a23c1001a2323c21a23c3233e233f8120af06002a494905002a49490700b53a03",
- 6: "062004010002b7a60c26050242420c68656c6c6f20776f726c6421070123456789abcd208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292b0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f23102311231223132314181b1c28171615400003290349483403350222231d4a484848482b50512a632223524100034200004322602261222704634848222862482864286548482228246628226723286828692322700048482371004848361c0037001a0031183119311b311d311e311f312023221e312131223123312431253126312731283129312a312b312c312d312e312f447825225314225427042455220824564c4d4b0222382124391c0081e80780046a6f686e2281d00f23241f880003420001892224902291922494249593a0a1a2a3a4a5a6a7a8a9aaabacadae24af3a00003b003c003d816472064e014f012a57000823810858235b235a2359b03139330039b1b200b322c01a23c1001a2323c21a23c3233e233f8120af06002a494905002a49490700b53a03b6b7043cb8033a0c2349c4}",
+ 6: "062004010002b7a60c26050242420c68656c6c6f20776f726c6421070123456789abcd208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292b0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f23102311231223132314181b1c28171615400003290349483403350222231d4a484848482b50512a632223524100034200004322602261222704634848222862482864286548482228246628226723286828692322700048482371004848361c0037001a0031183119311b311d311e311f312023221e312131223123312431253126312731283129312a312b312c312d312e312f447825225314225427042455220824564c4d4b0222382124391c0081e80780046a6f686e2281d00f23241f880003420001892224902291922494249593a0a1a2a3a4a5a6a7a8a9aaabacadae24af3a00003b003c003d816472064e014f012a57000823810858235b235a2359b03139330039b1b200b322c01a23c1001a2323c21a23c3233e233f8120af06002a494905002a49490700b53a03b6b7043cb8033a0c5c002349c4",
}
func pseudoOp(opcode string) bool {
@@ -435,7 +436,7 @@ pop
require.Equal(t, ops1.Program, ops2.Program)
}
-type expect struct {
+type Expect struct {
l int
s string
}
@@ -453,7 +454,7 @@ func testMatch(t testing.TB, actual, expected string) {
}
}
-func testProg(t testing.TB, source string, ver uint64, expected ...expect) *OpStream {
+func testProg(t testing.TB, source string, ver uint64, expected ...Expect) *OpStream {
t.Helper()
program := strings.ReplaceAll(source, ";", "\n")
ops, err := AssembleStringWithVersion(program, ver)
@@ -524,7 +525,7 @@ func testLine(t *testing.T, line string, ver uint64, expected string) {
testProg(t, source, ver)
return
}
- testProg(t, source, ver, expect{2, expected})
+ testProg(t, source, ver, Expect{2, expected})
}
func TestAssembleTxna(t *testing.T) {
@@ -574,7 +575,7 @@ int 1
+
// comment
`
- testProg(t, source, AssemblerMaxVersion, expect{3, "+ arg 0 wanted type uint64 got []byte"})
+ testProg(t, source, AssemblerMaxVersion, Expect{3, "+ arg 0 wanted type uint64 got []byte"})
}
// mutateProgVersion replaces version (first two symbols) in hex-encoded program
@@ -713,10 +714,10 @@ func TestAssembleBytes(t *testing.T) {
}
for _, b := range bad {
- testProg(t, b[0], v, expect{1, b[1]})
+ testProg(t, b[0], v, Expect{1, b[1]})
// pushbytes should produce the same errors
if v >= 3 {
- testProg(t, strings.Replace(b[0], "byte", "pushbytes", 1), v, expect{1, b[1]})
+ testProg(t, strings.Replace(b[0], "byte", "pushbytes", 1), v, Expect{1, b[1]})
}
}
})
@@ -1185,7 +1186,7 @@ bnz wat
int 2`
for v := uint64(1); v < backBranchEnabledVersion; v++ {
t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) {
- testProg(t, source, v, expect{3, "label \"wat\" is a back reference..."})
+ testProg(t, source, v, Expect{3, "label \"wat\" is a back reference..."})
})
}
for v := uint64(backBranchEnabledVersion); v <= AssemblerMaxVersion; v++ {
@@ -1239,7 +1240,7 @@ bnz nowhere
int 2`
for v := uint64(1); v <= AssemblerMaxVersion; v++ {
t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) {
- testProg(t, source, v, expect{2, "reference to undefined label \"nowhere\""})
+ testProg(t, source, v, Expect{2, "reference to undefined label \"nowhere\""})
})
}
}
@@ -1272,8 +1273,8 @@ int 2`
for v := uint64(1); v <= AssemblerMaxVersion; v++ {
t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) {
testProg(t, source, v,
- expect{2, "reference to undefined label \"nowhere\""},
- expect{4, "txn unknown field: \"XYZ\""})
+ Expect{2, "reference to undefined label \"nowhere\""},
+ Expect{4, "txn unknown field: \"XYZ\""})
})
}
}
@@ -1470,15 +1471,15 @@ func TestConstantArgs(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()
for v := uint64(1); v <= AssemblerMaxVersion; v++ {
- testProg(t, "int", v, expect{1, "int needs one argument"})
- testProg(t, "intc", v, expect{1, "intc operation needs one argument"})
- testProg(t, "byte", v, expect{1, "byte operation needs byte literal argument"})
- testProg(t, "bytec", v, expect{1, "bytec operation needs one argument"})
- testProg(t, "addr", v, expect{1, "addr operation needs one argument"})
+ testProg(t, "int", v, Expect{1, "int needs one argument"})
+ testProg(t, "intc", v, Expect{1, "intc operation needs one argument"})
+ testProg(t, "byte", v, Expect{1, "byte operation needs byte literal argument"})
+ testProg(t, "bytec", v, Expect{1, "bytec operation needs one argument"})
+ testProg(t, "addr", v, Expect{1, "addr operation needs one argument"})
}
for v := uint64(3); v <= AssemblerMaxVersion; v++ {
- testProg(t, "pushint", v, expect{1, "pushint needs one argument"})
- testProg(t, "pushbytes", v, expect{1, "pushbytes operation needs byte literal argument"})
+ testProg(t, "pushint", v, Expect{1, "pushint needs one argument"})
+ testProg(t, "pushbytes", v, Expect{1, "pushbytes operation needs byte literal argument"})
}
}
@@ -1615,7 +1616,7 @@ balance
int 1
==`
for v := uint64(2); v < directRefEnabledVersion; v++ {
- testProg(t, source, v, expect{2, "balance arg 0 wanted type uint64 got []byte"})
+ testProg(t, source, v, Expect{2, "balance arg 0 wanted type uint64 got []byte"})
}
for v := uint64(directRefEnabledVersion); v <= AssemblerMaxVersion; v++ {
testProg(t, source, v)
@@ -1632,7 +1633,7 @@ min_balance
int 1
==`
for v := uint64(3); v < directRefEnabledVersion; v++ {
- testProg(t, source, v, expect{2, "min_balance arg 0 wanted type uint64 got []byte"})
+ testProg(t, source, v, Expect{2, "min_balance arg 0 wanted type uint64 got []byte"})
}
for v := uint64(directRefEnabledVersion); v <= AssemblerMaxVersion; v++ {
testProg(t, source, v)
@@ -1646,16 +1647,16 @@ func TestAssembleAsset(t *testing.T) {
introduction := OpsByName[LogicVersion]["asset_holding_get"].Version
for v := introduction; v <= AssemblerMaxVersion; v++ {
testProg(t, "asset_holding_get ABC 1", v,
- expect{1, "asset_holding_get ABC 1 expects 2 stack arguments..."})
+ Expect{1, "asset_holding_get ABC 1 expects 2 stack arguments..."})
testProg(t, "int 1; asset_holding_get ABC 1", v,
- expect{2, "asset_holding_get ABC 1 expects 2 stack arguments..."})
+ Expect{2, "asset_holding_get ABC 1 expects 2 stack arguments..."})
testProg(t, "int 1; int 1; asset_holding_get ABC 1", v,
- expect{3, "asset_holding_get expects one argument"})
+ Expect{3, "asset_holding_get expects one argument"})
testProg(t, "int 1; int 1; asset_holding_get ABC", v,
- expect{3, "asset_holding_get unknown field: \"ABC\""})
+ Expect{3, "asset_holding_get unknown field: \"ABC\""})
testProg(t, "byte 0x1234; asset_params_get ABC 1", v,
- expect{2, "asset_params_get ABC 1 arg 0 wanted type uint64..."})
+ Expect{2, "asset_params_get ABC 1 arg 0 wanted type uint64..."})
testLine(t, "asset_params_get ABC 1", v, "asset_params_get expects one argument")
testLine(t, "asset_params_get ABC", v, "asset_params_get unknown field: \"ABC\"")
@@ -2039,15 +2040,15 @@ func TestPragmas(t *testing.T) {
}
testProg(t, `#pragma version 100`, assemblerNoVersion,
- expect{1, "unsupported version: 100"})
+ Expect{1, "unsupported version: 100"})
- testProg(t, `int 1`, 99, expect{0, "Can not assemble version 99"})
+ testProg(t, `int 1`, 99, Expect{0, "Can not assemble version 99"})
// Allow this on the off chance someone needs to reassemble an old logigsig
testProg(t, `#pragma version 0`, assemblerNoVersion)
testProg(t, `#pragma version a`, assemblerNoVersion,
- expect{1, `bad #pragma version: "a"`})
+ Expect{1, `bad #pragma version: "a"`})
// will default to 1
ops := testProg(t, "int 3", assemblerNoVersion)
@@ -2061,24 +2062,24 @@ func TestPragmas(t *testing.T) {
require.Equal(t, uint64(2), ops.Version)
// changing version is not allowed
- testProg(t, "#pragma version 1", 2, expect{1, "version mismatch..."})
- testProg(t, "#pragma version 2", 1, expect{1, "version mismatch..."})
+ testProg(t, "#pragma version 1", 2, Expect{1, "version mismatch..."})
+ testProg(t, "#pragma version 2", 1, Expect{1, "version mismatch..."})
testProg(t, "#pragma version 2\n#pragma version 1", assemblerNoVersion,
- expect{2, "version mismatch..."})
+ Expect{2, "version mismatch..."})
// repetitive, but fine
ops = testProg(t, "#pragma version 2\n#pragma version 2", assemblerNoVersion)
require.Equal(t, uint64(2), ops.Version)
testProg(t, "\nint 1\n#pragma version 2", assemblerNoVersion,
- expect{3, "#pragma version is only allowed before instructions"})
+ Expect{3, "#pragma version is only allowed before instructions"})
testProg(t, "#pragma run-mode 2", assemblerNoVersion,
- expect{1, `unsupported pragma directive: "run-mode"`})
+ Expect{1, `unsupported pragma directive: "run-mode"`})
testProg(t, "#pragma versions", assemblerNoVersion,
- expect{1, `unsupported pragma directive: "versions"`})
+ Expect{1, `unsupported pragma directive: "versions"`})
ops = testProg(t, "#pragma version 1", assemblerNoVersion)
require.Equal(t, uint64(1), ops.Version)
@@ -2086,10 +2087,10 @@ func TestPragmas(t *testing.T) {
ops = testProg(t, "\n#pragma version 1", assemblerNoVersion)
require.Equal(t, uint64(1), ops.Version)
- testProg(t, "#pragma", assemblerNoVersion, expect{1, "empty pragma"})
+ testProg(t, "#pragma", assemblerNoVersion, Expect{1, "empty pragma"})
testProg(t, "#pragma version", assemblerNoVersion,
- expect{1, "no version value"})
+ Expect{1, "no version value"})
ops = testProg(t, " #pragma version 5 ", assemblerNoVersion)
require.Equal(t, uint64(5), ops.Version)
@@ -2108,8 +2109,8 @@ int 1
require.NoError(t, err)
require.Equal(t, ops1.Program, ops.Program)
- testProg(t, text, 0, expect{1, "version mismatch..."})
- testProg(t, text, 2, expect{1, "version mismatch..."})
+ testProg(t, text, 0, Expect{1, "version mismatch..."})
+ testProg(t, text, 2, Expect{1, "version mismatch..."})
testProg(t, text, assemblerNoVersion)
ops, err = AssembleStringWithVersion(text, assemblerNoVersion)
@@ -2125,8 +2126,8 @@ int 1
require.NoError(t, err)
require.Equal(t, ops2.Program, ops.Program)
- testProg(t, text, 0, expect{1, "version mismatch..."})
- testProg(t, text, 1, expect{1, "version mismatch..."})
+ testProg(t, text, 0, Expect{1, "version mismatch..."})
+ testProg(t, text, 1, Expect{1, "version mismatch..."})
ops, err = AssembleStringWithVersion(text, assemblerNoVersion)
require.NoError(t, err)
@@ -2146,7 +2147,7 @@ len
require.Equal(t, ops2.Program, ops.Program)
testProg(t, "#pragma unk", assemblerNoVersion,
- expect{1, `unsupported pragma directive: "unk"`})
+ Expect{1, `unsupported pragma directive: "unk"`})
}
func TestAssembleConstants(t *testing.T) {
@@ -2218,29 +2219,29 @@ func TestSwapTypeCheck(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()
/* reconfirm that we detect this type error */
- testProg(t, "int 1; byte 0x1234; +", AssemblerMaxVersion, expect{3, "+ arg 1..."})
+ testProg(t, "int 1; byte 0x1234; +", AssemblerMaxVersion, Expect{3, "+ arg 1..."})
/* despite swap, we track types */
- testProg(t, "int 1; byte 0x1234; swap; +", AssemblerMaxVersion, expect{4, "+ arg 0..."})
- testProg(t, "byte 0x1234; int 1; swap; +", AssemblerMaxVersion, expect{4, "+ arg 1..."})
+ testProg(t, "int 1; byte 0x1234; swap; +", AssemblerMaxVersion, Expect{4, "+ arg 0..."})
+ testProg(t, "byte 0x1234; int 1; swap; +", AssemblerMaxVersion, Expect{4, "+ arg 1..."})
}
func TestDigAsm(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()
- testProg(t, "int 1; dig; +", AssemblerMaxVersion, expect{2, "dig expects 1 immediate..."})
- testProg(t, "int 1; dig junk; +", AssemblerMaxVersion, expect{2, "dig unable to parse..."})
+ testProg(t, "int 1; dig; +", AssemblerMaxVersion, Expect{2, "dig expects 1 immediate..."})
+ testProg(t, "int 1; dig junk; +", AssemblerMaxVersion, Expect{2, "dig unable to parse..."})
testProg(t, "int 1; byte 0x1234; int 2; dig 2; +", AssemblerMaxVersion)
testProg(t, "byte 0x32; byte 0x1234; int 2; dig 2; +", AssemblerMaxVersion,
- expect{5, "+ arg 1..."})
+ Expect{5, "+ arg 1..."})
testProg(t, "byte 0x32; byte 0x1234; int 2; dig 3; +", AssemblerMaxVersion,
- expect{4, "dig 3 expects 4..."})
+ Expect{4, "dig 3 expects 4..."})
testProg(t, "int 1; byte 0x1234; int 2; dig 12; +", AssemblerMaxVersion,
- expect{4, "dig 12 expects 13..."})
+ Expect{4, "dig 12 expects 13..."})
// Confirm that digging something out does not ruin our knowledge about the types in the middle
testProg(t, "int 1; byte 0x1234; byte 0x1234; dig 2; dig 3; +; pop; +", AssemblerMaxVersion,
- expect{8, "+ arg 1..."})
+ Expect{8, "+ arg 1..."})
testProg(t, "int 3; pushbytes \"123456\"; int 1; dig 2; substring3", AssemblerMaxVersion)
}
@@ -2248,39 +2249,39 @@ func TestDigAsm(t *testing.T) {
func TestEqualsTypeCheck(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()
- testProg(t, "int 1; byte 0x1234; ==", AssemblerMaxVersion, expect{3, "== arg 0..."})
- testProg(t, "int 1; byte 0x1234; !=", AssemblerMaxVersion, expect{3, "!= arg 0..."})
- testProg(t, "byte 0x1234; int 1; ==", AssemblerMaxVersion, expect{3, "== arg 0..."})
- testProg(t, "byte 0x1234; int 1; !=", AssemblerMaxVersion, expect{3, "!= arg 0..."})
+ testProg(t, "int 1; byte 0x1234; ==", AssemblerMaxVersion, Expect{3, "== arg 0..."})
+ testProg(t, "int 1; byte 0x1234; !=", AssemblerMaxVersion, Expect{3, "!= arg 0..."})
+ testProg(t, "byte 0x1234; int 1; ==", AssemblerMaxVersion, Expect{3, "== arg 0..."})
+ testProg(t, "byte 0x1234; int 1; !=", AssemblerMaxVersion, Expect{3, "!= arg 0..."})
}
func TestDupTypeCheck(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()
- testProg(t, "byte 0x1234; dup; int 1; +", AssemblerMaxVersion, expect{4, "+ arg 0..."})
+ testProg(t, "byte 0x1234; dup; int 1; +", AssemblerMaxVersion, Expect{4, "+ arg 0..."})
testProg(t, "byte 0x1234; int 1; dup; +", AssemblerMaxVersion)
- testProg(t, "byte 0x1234; int 1; dup2; +", AssemblerMaxVersion, expect{4, "+ arg 0..."})
- testProg(t, "int 1; byte 0x1234; dup2; +", AssemblerMaxVersion, expect{4, "+ arg 1..."})
+ testProg(t, "byte 0x1234; int 1; dup2; +", AssemblerMaxVersion, Expect{4, "+ arg 0..."})
+ testProg(t, "int 1; byte 0x1234; dup2; +", AssemblerMaxVersion, Expect{4, "+ arg 1..."})
- testProg(t, "byte 0x1234; int 1; dup; dig 1; len", AssemblerMaxVersion, expect{5, "len arg 0..."})
- testProg(t, "int 1; byte 0x1234; dup; dig 1; !", AssemblerMaxVersion, expect{5, "! arg 0..."})
+ testProg(t, "byte 0x1234; int 1; dup; dig 1; len", AssemblerMaxVersion, Expect{5, "len arg 0..."})
+ testProg(t, "int 1; byte 0x1234; dup; dig 1; !", AssemblerMaxVersion, Expect{5, "! arg 0..."})
- testProg(t, "byte 0x1234; int 1; dup2; dig 2; len", AssemblerMaxVersion, expect{5, "len arg 0..."})
- testProg(t, "int 1; byte 0x1234; dup2; dig 2; !", AssemblerMaxVersion, expect{5, "! arg 0..."})
+ testProg(t, "byte 0x1234; int 1; dup2; dig 2; len", AssemblerMaxVersion, Expect{5, "len arg 0..."})
+ testProg(t, "int 1; byte 0x1234; dup2; dig 2; !", AssemblerMaxVersion, Expect{5, "! arg 0..."})
}
func TestSelectTypeCheck(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()
- testProg(t, "int 1; int 2; int 3; select; len", AssemblerMaxVersion, expect{5, "len arg 0..."})
- testProg(t, "byte 0x1234; byte 0x5678; int 3; select; !", AssemblerMaxVersion, expect{5, "! arg 0..."})
+ testProg(t, "int 1; int 2; int 3; select; len", AssemblerMaxVersion, Expect{5, "len arg 0..."})
+ testProg(t, "byte 0x1234; byte 0x5678; int 3; select; !", AssemblerMaxVersion, Expect{5, "! arg 0..."})
}
func TestSetBitTypeCheck(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()
- testProg(t, "int 1; int 2; int 3; setbit; len", AssemblerMaxVersion, expect{5, "len arg 0..."})
- testProg(t, "byte 0x1234; int 2; int 3; setbit; !", AssemblerMaxVersion, expect{5, "! arg 0..."})
+ testProg(t, "int 1; int 2; int 3; setbit; len", AssemblerMaxVersion, Expect{5, "len arg 0..."})
+ testProg(t, "byte 0x1234; int 2; int 3; setbit; !", AssemblerMaxVersion, Expect{5, "! arg 0..."})
}
func TestCoverAsm(t *testing.T) {
@@ -2288,7 +2289,7 @@ func TestCoverAsm(t *testing.T) {
t.Parallel()
testProg(t, `int 4; byte "john"; int 5; cover 2; pop; +`, AssemblerMaxVersion)
testProg(t, `int 4; byte "ayush"; int 5; cover 1; pop; +`, AssemblerMaxVersion)
- testProg(t, `int 4; byte "john"; int 5; cover 2; +`, AssemblerMaxVersion, expect{5, "+ arg 1..."})
+ testProg(t, `int 4; byte "john"; int 5; cover 2; +`, AssemblerMaxVersion, Expect{5, "+ arg 1..."})
}
@@ -2298,15 +2299,15 @@ func TestUncoverAsm(t *testing.T) {
testProg(t, `int 4; byte "john"; int 5; uncover 2; +`, AssemblerMaxVersion)
testProg(t, `int 4; byte "ayush"; int 5; uncover 1; pop; +`, AssemblerMaxVersion)
testProg(t, `int 1; byte "jj"; byte "ayush"; byte "john"; int 5; uncover 4; +`, AssemblerMaxVersion)
- testProg(t, `int 4; byte "ayush"; int 5; uncover 1; +`, AssemblerMaxVersion, expect{5, "+ arg 1..."})
+ testProg(t, `int 4; byte "ayush"; int 5; uncover 1; +`, AssemblerMaxVersion, Expect{5, "+ arg 1..."})
}
func TestTxTypes(t *testing.T) {
- testProg(t, "itxn_begin; itxn_field Sender", 5, expect{2, "itxn_field Sender expects 1 stack argument..."})
- testProg(t, "itxn_begin; int 1; itxn_field Sender", 5, expect{3, "...wanted type []byte got uint64"})
+ testProg(t, "itxn_begin; itxn_field Sender", 5, Expect{2, "itxn_field Sender expects 1 stack argument..."})
+ testProg(t, "itxn_begin; int 1; itxn_field Sender", 5, Expect{3, "...wanted type []byte got uint64"})
testProg(t, "itxn_begin; byte 0x56127823; itxn_field Sender", 5)
- testProg(t, "itxn_begin; itxn_field Amount", 5, expect{2, "itxn_field Amount expects 1 stack argument..."})
- testProg(t, "itxn_begin; byte 0x87123376; itxn_field Amount", 5, expect{3, "...wanted type uint64 got []byte"})
+ testProg(t, "itxn_begin; itxn_field Amount", 5, Expect{2, "itxn_field Amount expects 1 stack argument..."})
+ testProg(t, "itxn_begin; byte 0x87123376; itxn_field Amount", 5, Expect{3, "...wanted type uint64 got []byte"})
testProg(t, "itxn_begin; int 1; itxn_field Amount", 5)
}
diff --git a/data/transactions/logic/backwardCompat_test.go b/data/transactions/logic/backwardCompat_test.go
index 8fb9109d6a..95d11d9cc8 100644
--- a/data/transactions/logic/backwardCompat_test.go
+++ b/data/transactions/logic/backwardCompat_test.go
@@ -468,15 +468,15 @@ func TestBackwardCompatAssemble(t *testing.T) {
source := "int 1; int 1; bnz done; done:"
t.Run("v=default", func(t *testing.T) {
- testProg(t, source, assemblerNoVersion, expect{4, "label \"done\" is too far away"})
+ testProg(t, source, assemblerNoVersion, Expect{4, "label \"done\" is too far away"})
})
t.Run("v=default", func(t *testing.T) {
- testProg(t, source, 0, expect{4, "label \"done\" is too far away"})
+ testProg(t, source, 0, Expect{4, "label \"done\" is too far away"})
})
t.Run("v=default", func(t *testing.T) {
- testProg(t, source, 1, expect{4, "label \"done\" is too far away"})
+ testProg(t, source, 1, Expect{4, "label \"done\" is too far away"})
})
for v := uint64(2); v <= AssemblerMaxVersion; v++ {
diff --git a/data/transactions/logic/blackbox_test.go b/data/transactions/logic/blackbox_test.go
index 7761ba81d1..79e57a74cf 100644
--- a/data/transactions/logic/blackbox_test.go
+++ b/data/transactions/logic/blackbox_test.go
@@ -80,7 +80,7 @@ func TestNewAppEvalParams(t *testing.T) {
for i, param := range params {
for j, testCase := range cases {
t.Run(fmt.Sprintf("i=%d,j=%d", i, j), func(t *testing.T) {
- ep := logic.NewAppEvalParams(testCase.group, ¶m, nil, nil)
+ ep := logic.NewAppEvalParams(testCase.group, ¶m, nil, 0)
// Ensure non app calls have a nil evaluator, and that non-nil
// evaluators point to the right transactions and values
diff --git a/data/transactions/logic/doc.go b/data/transactions/logic/doc.go
index 481887e520..bf56ad5f98 100644
--- a/data/transactions/logic/doc.go
+++ b/data/transactions/logic/doc.go
@@ -136,6 +136,7 @@ var opDocByName = map[string]string{
"extract_uint16": "pop a byte-array A and integer B. Extract a range of bytes from A starting at B up to but not including B+2, convert bytes as big endian and push the uint64 result. If B+2 is larger than the array length, the program fails",
"extract_uint32": "pop a byte-array A and integer B. Extract a range of bytes from A starting at B up to but not including B+4, convert bytes as big endian and push the uint64 result. If B+4 is larger than the array length, the program fails",
"extract_uint64": "pop a byte-array A and integer B. Extract a range of bytes from A starting at B up to but not including B+8, convert bytes as big endian and push the uint64 result. If B+8 is larger than the array length, the program fails",
+ "base64_decode": "decode X which was base64-encoded using _encoding alphabet_ E. Fail if X is not base64 encoded with alphabet E",
"balance": "get balance for account A, in microalgos. The balance is observed after the effects of previous transactions in the group, and after the fee for the current transaction is deducted.",
"min_balance": "get minimum required balance for account A, in microalgos. Required balance is affected by [ASA](https://developer.algorand.org/docs/features/asa/#assets-overview) and [App](https://developer.algorand.org/docs/features/asc1/stateful/#minimum-balance-requirement-for-a-smart-contract) usage. When creating or opting into an app, the minimum balance grows before the app code runs, therefore the increase is visible there. When deleting or closing out, the minimum balance decreases after the app executes.",
@@ -234,6 +235,8 @@ var opcodeImmediateNotes = map[string]string{
"ecdsa_verify": "{uint8 curve index}",
"ecdsa_pk_decompress": "{uint8 curve index}",
"ecdsa_pk_recover": "{uint8 curve index}",
+
+ "base64_decode": "{uint8 alphabet index}",
}
// OpImmediateNote returns a short string about immediate data which follows the op byte
@@ -288,6 +291,7 @@ var opDocExtras = map[string]string{
"itxn_begin": "`itxn_begin` initializes Sender to the application address; Fee to the minimum allowable, taking into account MinTxnFee and credit from overpaying in earlier transactions; FirstValid/LastValid to the values in the top-level transaction, and all other fields to zero values.",
"itxn_field": "`itxn_field` fails if X is of the wrong type for F, including a byte array of the wrong size for use as an address when F is an address field. `itxn_field` also fails if X is an account or asset that does not appear in `txn.Accounts` or `txn.ForeignAssets` of the top-level transaction. (Setting addresses in asset creation are exempted from this requirement.)",
"itxn_submit": "`itxn_submit` resets the current transaction so that it can not be resubmitted. A new `itxn_begin` is required to prepare another inner transaction.",
+ "base64_decode": "decodes X using the base64 encoding alphabet E. Specify the alphabet with an immediate arg either as URL and Filename Safe (`URLAlph`) or Standard (`StdAlph`). See RFC 4648 (sections 4 and 5)",
}
// OpDocExtra returns extra documentation text about an op
@@ -300,7 +304,7 @@ func OpDocExtra(opName string) string {
// opcodes consecutively, even if their opcode values are not.
var OpGroups = map[string][]string{
"Arithmetic": {"sha256", "keccak256", "sha512_256", "ed25519verify", "ecdsa_verify", "ecdsa_pk_recover", "ecdsa_pk_decompress", "+", "-", "/", "*", "<", ">", "<=", ">=", "&&", "||", "shl", "shr", "sqrt", "bitlen", "exp", "==", "!=", "!", "len", "itob", "btoi", "%", "|", "&", "^", "~", "mulw", "addw", "divmodw", "expw", "getbit", "setbit", "getbyte", "setbyte", "concat"},
- "Byte Array Slicing": {"substring", "substring3", "extract", "extract3", "extract_uint16", "extract_uint32", "extract_uint64"},
+ "Byte Array Slicing": {"substring", "substring3", "extract", "extract3", "extract_uint16", "extract_uint32", "extract_uint64", "base64_decode"},
"Byte Array Arithmetic": {"b+", "b-", "b/", "b*", "b<", "b>", "b<=", "b>=", "b==", "b!=", "b%"},
"Byte Array Logic": {"b|", "b&", "b^", "b~"},
"Loading Values": {"intcblock", "intc", "intc_0", "intc_1", "intc_2", "intc_3", "pushint", "bytecblock", "bytec", "bytec_0", "bytec_1", "bytec_2", "bytec_3", "pushbytes", "bzero", "arg", "arg_0", "arg_1", "arg_2", "arg_3", "args", "txn", "gtxn", "txna", "txnas", "gtxna", "gtxnas", "gtxns", "gtxnsa", "gtxnsas", "global", "load", "loads", "store", "stores", "gload", "gloads", "gloadss", "gaid", "gaids"},
diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go
index a711a1d10d..749942a36a 100644
--- a/data/transactions/logic/eval.go
+++ b/data/transactions/logic/eval.go
@@ -20,6 +20,7 @@ import (
"bytes"
"crypto/sha256"
"crypto/sha512"
+ "encoding/base64"
"encoding/binary"
"encoding/hex"
"errors"
@@ -209,19 +210,19 @@ type LedgerForLogic interface {
AssetHolding(addr basics.Address, assetIdx basics.AssetIndex) (basics.AssetHolding, error)
AssetParams(aidx basics.AssetIndex) (basics.AssetParams, basics.Address, error)
AppParams(aidx basics.AppIndex) (basics.AppParams, basics.Address, error)
- ApplicationID() basics.AppIndex
OptedIn(addr basics.Address, appIdx basics.AppIndex) (bool, error)
GetCreatableID(groupIdx int) basics.CreatableIndex
GetLocal(addr basics.Address, appIdx basics.AppIndex, key string, accountIdx uint64) (value basics.TealValue, exists bool, err error)
- SetLocal(addr basics.Address, key string, value basics.TealValue, accountIdx uint64) error
- DelLocal(addr basics.Address, key string, accountIdx uint64) error
+ SetLocal(addr basics.Address, appIdx basics.AppIndex, key string, value basics.TealValue, accountIdx uint64) error
+ DelLocal(addr basics.Address, appIdx basics.AppIndex, key string, accountIdx uint64) error
GetGlobal(appIdx basics.AppIndex, key string) (value basics.TealValue, exists bool, err error)
- SetGlobal(key string, value basics.TealValue) error
- DelGlobal(key string) error
+ SetGlobal(appIdx basics.AppIndex, key string, value basics.TealValue) error
+ DelGlobal(appIdx basics.AppIndex, key string) error
Perform(gi int, ep *EvalParams) error
+ Counter() uint64
}
// EvalSideEffects contains data returned from evaluation
@@ -281,9 +282,15 @@ type EvalParams struct {
// Total pool of app call budget in a group transaction (nil before pooling enabled)
PooledApplicationBudget *uint64
- // The "call stack" of apps calling this app.
- appStack []basics.AppIndex
- caller *EvalContext
+ // Total allowable inner txns in a group transaction (nil before pooling enabled)
+ pooledAllowedInners *int
+
+ // If non-zero, a txn counter value after which assets and apps should be
+ // allowed w/o foreign array. Left zero until createdResourcesVersion.
+ initialCounter uint64
+
+ // The calling context, if this is an inner app call
+ caller *EvalContext
}
func copyWithClearAD(txgroup []transactions.SignedTxnWithAD) []transactions.SignedTxnWithAD {
@@ -295,40 +302,41 @@ func copyWithClearAD(txgroup []transactions.SignedTxnWithAD) []transactions.Sign
return copy
}
-// NewAppEvalParams creates an EvalParams to be used while evaluating apps in txgroup
-func NewAppEvalParams(txgroup []transactions.SignedTxnWithAD, proto *config.ConsensusParams, specials *transactions.SpecialAddresses, caller *EvalContext) *EvalParams {
- minTealVersion := ComputeMinTealVersion(txgroup, caller != nil)
- if minTealVersion == 0 && caller == nil {
+// NewAppEvalParams creates an EvalParams to be used while evaluating apps for a top-level txgroup
+func NewAppEvalParams(txgroup []transactions.SignedTxnWithAD, proto *config.ConsensusParams, specials *transactions.SpecialAddresses, counter uint64) *EvalParams {
+ minTealVersion := ComputeMinTealVersion(txgroup, false)
+
+ apps := 0
+ for _, tx := range txgroup {
+ if tx.Txn.Type == protocol.ApplicationCallTx {
+ apps++
+ }
+ }
+ if apps == 0 {
// As an optimization, do no work if there will be no apps to evaluate in this txgroup
- // Inner transactions always use the EvalParams (for specials), so don't skip for them.
return nil
}
- var budget uint64
- pooledApplicationBudget := &budget
-
- var appStack []basics.AppIndex
- var ledger LedgerForLogic
- if caller != nil {
- // share pool in the inner calls
- pooledApplicationBudget = caller.PooledApplicationBudget
- // note the stack, to prevent re-entrancy
- appStack = caller.appStack
- ledger = caller.Ledger
- }
+ var pooledApplicationBudget *uint64
+ var pooledAllowedInners *int
credit, _ := transactions.FeeCredit(txgroup, proto.MinTxnFee)
+
if proto.EnableAppCostPooling {
- for _, tx := range txgroup {
- if tx.Txn.Type == protocol.ApplicationCallTx {
- *pooledApplicationBudget += uint64(proto.MaxAppProgramCost)
- }
- }
- } else {
- pooledApplicationBudget = nil
+ pooledApplicationBudget = new(uint64)
+ *pooledApplicationBudget = uint64(apps * proto.MaxAppProgramCost)
+ }
+
+ if proto.EnableInnerTransactionPooling {
+ pooledAllowedInners = new(int)
+ *pooledAllowedInners = apps * proto.MaxInnerTransactions
}
- return &EvalParams{
+ if counter == 0 || proto.LogicSigVersion < createdResourcesVersion {
+ counter = math.MaxUint64
+ }
+
+ ep := &EvalParams{
Proto: proto,
TxnGroup: copyWithClearAD(txgroup),
PastSideEffects: MakePastSideEffects(len(txgroup)),
@@ -336,10 +344,47 @@ func NewAppEvalParams(txgroup []transactions.SignedTxnWithAD, proto *config.Cons
FeeCredit: &credit,
Specials: specials,
PooledApplicationBudget: pooledApplicationBudget,
- appStack: appStack,
+ pooledAllowedInners: pooledAllowedInners,
+ initialCounter: counter,
+ }
+ return ep
+}
+
+// NewInnerEvalParams creates an EvalParams to be used while evaluating an inner group txgroup
+func NewInnerEvalParams(txg []transactions.SignedTxn, caller *EvalContext) *EvalParams {
+ txgroup := transactions.WrapSignedTxnsWithAD(txg)
+
+ minTealVersion := ComputeMinTealVersion(txgroup, true)
+ // Can't happen now, since innerAppsEnabledVersion > than any minimum
+ // imposed otherwise. But is correct to check.
+ if minTealVersion < *caller.MinTealVersion {
+ minTealVersion = *caller.MinTealVersion
+ }
+ credit, _ := transactions.FeeCredit(txgroup, caller.Proto.MinTxnFee)
+ *caller.FeeCredit = basics.AddSaturate(*caller.FeeCredit, credit)
+
+ if caller.Proto.EnableAppCostPooling {
+ for _, tx := range txgroup {
+ if tx.Txn.Type == protocol.ApplicationCallTx {
+ *caller.PooledApplicationBudget += uint64(caller.Proto.MaxAppProgramCost)
+ }
+ }
+ }
+
+ ep := &EvalParams{
+ Proto: caller.Proto,
+ TxnGroup: copyWithClearAD(txgroup),
+ PastSideEffects: MakePastSideEffects(len(txgroup)),
+ MinTealVersion: &minTealVersion,
+ FeeCredit: caller.FeeCredit,
+ Specials: caller.Specials,
+ PooledApplicationBudget: caller.PooledApplicationBudget,
+ pooledAllowedInners: caller.pooledAllowedInners,
+ Ledger: caller.Ledger,
+ initialCounter: caller.initialCounter,
caller: caller,
- Ledger: ledger,
}
+ return ep
}
type opEvalFunc func(cx *EvalContext)
@@ -398,9 +443,19 @@ type EvalContext struct {
// the transaction being evaluated (initialized from GroupIndex + ep.TxnGroup)
Txn *transactions.SignedTxnWithAD
+ // Txn.EvalDelta maintains a summary of changes as we go. We used to
+ // compute this from the ledger after a full eval. But now apps can call
+ // apps. When they do, all of the changes accumulate into the parent's
+ // ledger, but Txn.EvalDelta should only have the changes from *this*
+ // call. (The changes caused by children are deeper inside - in the
+ // EvalDeltas of the InnerTxns inside this EvalDelta) Nice bonus - by
+ // keeping the running changes, the debugger can be changed to display them
+ // as the app runs.
+
stack []stackValue
callstack []int
+ appID basics.AppIndex
program []byte
pc int
nextpc int
@@ -414,15 +469,6 @@ type EvalContext struct {
cost int // cost incurred so far
logSize int // total log size so far
- // Summary of changes. We used to compute this from the ledger after a full
- // eval. But now apps can call apps. When they do, all of the changes
- // accumulate into the parent's ledger, but EvalDelta should only have the
- // changes from *this* call. (The changes caused by children are deeper
- // inside - in the EvalDeltas of the InnerTxns inside this EvalDelta) Nice
- // bonus - by keep the running changes, the debugger can be changed to
- // display them as the app runs.
- //EvalDelta transactions.EvalDelta
-
// Set of PC values that branches we've seen so far might
// go. So, if checkStep() skips one, that branch is trying to
// jump into the middle of a multibyte instruction
@@ -490,8 +536,8 @@ func (pe PanicError) Error() string {
var errLogicSigNotSupported = errors.New("LogicSig not supported")
var errTooManyArgs = errors.New("LogicSig has too many arguments")
-// EvalContract executes stateful TEAL program as the ith transaction in params
-func EvalContract(program []byte, gi int, params *EvalParams) (bool, *EvalContext, error) {
+// EvalContract executes stateful TEAL program as the gi'th transaction in params
+func EvalContract(program []byte, gi int, aid basics.AppIndex, params *EvalParams) (bool, *EvalContext, error) {
if params.Ledger == nil {
return false, nil, errors.New("no ledger in contract eval")
}
@@ -500,17 +546,18 @@ func EvalContract(program []byte, gi int, params *EvalParams) (bool, *EvalContex
runModeFlags: runModeApplication,
GroupIndex: gi,
Txn: ¶ms.TxnGroup[gi],
+ appID: aid,
}
pass, err := eval(program, &cx)
- // The following two updates show a need for something like a
- // GroupEvalContext, as we are currently tucking things into the
- // EvalParams so that they are available to later calls.
-
// update pooled budget (shouldn't overflow, but being careful anyway)
if cx.PooledApplicationBudget != nil {
*cx.PooledApplicationBudget = basics.SubSaturate(*cx.PooledApplicationBudget, uint64(cx.cost))
}
+ // update allowed inner transactions (shouldn't overflow, but it's an int, so safe anyway)
+ if cx.pooledAllowedInners != nil {
+ *cx.pooledAllowedInners -= len(cx.Txn.EvalDelta.InnerTxns)
+ }
// update side effects
cx.PastSideEffects[cx.GroupIndex].setScratchSpace(cx.scratch)
@@ -518,8 +565,8 @@ func EvalContract(program []byte, gi int, params *EvalParams) (bool, *EvalContex
}
// EvalApp is a lighter weight interface that doesn't return the EvalContext
-func EvalApp(program []byte, gi int, params *EvalParams) (bool, error) {
- pass, _, err := EvalContract(program, gi, params)
+func EvalApp(program []byte, gi int, aid basics.AppIndex, params *EvalParams) (bool, error) {
+ pass, _, err := EvalContract(program, gi, aid, params)
return pass, err
}
@@ -627,7 +674,7 @@ func eval(program []byte, cx *EvalContext) (pass bool, err error) {
// static checks and reject programs that are invalid. Prior to v4,
// these static checks include a cost estimate that must be low enough
// (controlled by params.Proto).
-func CheckContract(program []byte, gi int, params *EvalParams) error {
+func CheckContract(program []byte, params *EvalParams) error {
return check(program, params, runModeApplication)
}
@@ -710,14 +757,12 @@ func versionCheck(program []byte, params *EvalParams) (uint64, int, error) {
return 0, 0, fmt.Errorf("program version %d greater than protocol supported version %d", version, params.Proto.LogicSigVersion)
}
- var minVersion uint64
if params.MinTealVersion == nil {
- minVersion = ComputeMinTealVersion(params.TxnGroup, params.caller != nil)
- } else {
- minVersion = *params.MinTealVersion
+ minVersion := ComputeMinTealVersion(params.TxnGroup, params.caller != nil)
+ params.MinTealVersion = &minVersion
}
- if version < minVersion {
- return 0, 0, fmt.Errorf("program version must be >= %d for this transaction group, but have version %d", minVersion, version)
+ if version < *params.MinTealVersion {
+ return 0, 0, fmt.Errorf("program version must be >= %d for this transaction group, but have version %d", *params.MinTealVersion, version)
}
return version, vlen, nil
}
@@ -756,6 +801,13 @@ func (cx *EvalContext) budget() int {
return cx.Proto.MaxAppProgramCost
}
+func (cx *EvalContext) allowedInners() int {
+ if cx.Proto.EnableInnerTransactionPooling && cx.pooledAllowedInners != nil {
+ return *cx.pooledAllowedInners
+ }
+ return cx.Proto.MaxInnerTransactions
+}
+
func (cx *EvalContext) step() {
opcode := cx.program[cx.pc]
spec := &opsByOpcode[cx.version][opcode]
@@ -2559,32 +2611,19 @@ func (cx *EvalContext) getLatestTimestamp() (timestamp uint64, err error) {
return uint64(ts), nil
}
-func (cx *EvalContext) getApplicationID() (uint64, error) {
- if cx.Ledger == nil {
- return 0, fmt.Errorf("ledger not available")
- }
- return uint64(cx.Ledger.ApplicationID()), nil
-}
-
-func (cx *EvalContext) getApplicationAddress() (basics.Address, error) {
- if cx.Ledger == nil {
- return basics.Address{}, fmt.Errorf("ledger not available")
- }
-
+func (cx *EvalContext) getApplicationAddress() basics.Address {
// Initialize appAddrCache if necessary
if cx.appAddrCache == nil {
cx.appAddrCache = make(map[basics.AppIndex]basics.Address)
}
- appID := cx.Ledger.ApplicationID()
- // Hashes are expensive, so we cache computed app addrs
- appAddr, ok := cx.appAddrCache[appID]
+ appAddr, ok := cx.appAddrCache[cx.appID]
if !ok {
- appAddr = appID.Address()
- cx.appAddrCache[appID] = appAddr
+ appAddr = cx.appID.Address()
+ cx.appAddrCache[cx.appID] = appAddr
}
- return appAddr, nil
+ return appAddr
}
func (cx *EvalContext) getCreatableID(groupIndex int) (cid uint64, err error) {
@@ -2599,7 +2638,7 @@ func (cx *EvalContext) getCreatorAddress() ([]byte, error) {
if cx.Ledger == nil {
return nil, fmt.Errorf("ledger not available")
}
- _, creator, err := cx.Ledger.AppParams(cx.Ledger.ApplicationID())
+ _, creator, err := cx.Ledger.AppParams(cx.appID)
if err != nil {
return nil, fmt.Errorf("No params for current app")
}
@@ -2627,10 +2666,10 @@ func (cx *EvalContext) globalFieldToValue(fs globalFieldSpec) (sv stackValue, er
case LatestTimestamp:
sv.Uint, err = cx.getLatestTimestamp()
case CurrentApplicationID:
- sv.Uint, err = cx.getApplicationID()
+ sv.Uint = uint64(cx.appID)
case CurrentApplicationAddress:
var addr basics.Address
- addr, err = cx.getApplicationAddress()
+ addr = cx.getApplicationAddress()
sv.Bytes = addr[:]
case CreatorAddress:
sv.Bytes, err = cx.getCreatorAddress()
@@ -2640,14 +2679,14 @@ func (cx *EvalContext) globalFieldToValue(fs globalFieldSpec) (sv stackValue, er
sv.Uint = uint64(cx.budget() - cx.cost)
case CallerApplicationID:
if cx.caller != nil {
- sv.Uint, err = cx.caller.getApplicationID()
+ sv.Uint = uint64(cx.caller.appID)
} else {
sv.Uint = 0
}
case CallerApplicationAddress:
if cx.caller != nil {
var addr basics.Address
- addr, err = cx.caller.getApplicationAddress()
+ addr = cx.caller.getApplicationAddress()
sv.Bytes = addr[:]
} else {
sv.Bytes = zeroAddress[:]
@@ -3265,7 +3304,7 @@ func (cx *EvalContext) accountReference(account stackValue) (basics.Address, uin
if err != nil {
// Application address is acceptable. index is meaningless though
- appAddr, _ := cx.getApplicationAddress()
+ appAddr := cx.getApplicationAddress()
if appAddr == addr {
return addr, uint64(len(cx.Txn.Txn.Accounts) + 1), nil
}
@@ -3502,7 +3541,7 @@ func opAppLocalPut(cx *EvalContext) {
// if writing the same value, do nothing, matching ledger behavior with
// previous BuildEvalDelta mechanism
- etv, ok, err := cx.Ledger.GetLocal(addr, cx.Ledger.ApplicationID(), key, accountIdx)
+ etv, ok, err := cx.Ledger.GetLocal(addr, cx.appID, key, accountIdx)
if err != nil {
cx.err = err
return
@@ -3515,7 +3554,7 @@ func opAppLocalPut(cx *EvalContext) {
}
cx.Txn.EvalDelta.LocalDeltas[accountIdx][key] = tv.ToValueDelta()
}
- err = cx.Ledger.SetLocal(addr, key, tv, accountIdx)
+ err = cx.Ledger.SetLocal(addr, cx.appID, key, tv, accountIdx)
if err != nil {
cx.err = err
return
@@ -3538,7 +3577,7 @@ func opAppGlobalPut(cx *EvalContext) {
// if writing the same value, do nothing, matching ledger behavior with
// previous BuildEvalDelta mechanism
- etv, ok, err := cx.Ledger.GetGlobal(cx.Ledger.ApplicationID(), key)
+ etv, ok, err := cx.Ledger.GetGlobal(cx.appID, key)
if err != nil {
cx.err = err
return
@@ -3548,7 +3587,7 @@ func opAppGlobalPut(cx *EvalContext) {
cx.Txn.EvalDelta.GlobalDelta[key] = tv.ToValueDelta()
}
- err = cx.Ledger.SetGlobal(key, tv)
+ err = cx.Ledger.SetGlobal(cx.appID, key, tv)
if err != nil {
cx.err = err
return
@@ -3576,7 +3615,7 @@ func opAppLocalDel(cx *EvalContext) {
cx.Txn.EvalDelta.LocalDeltas[accountIdx][key] = basics.ValueDelta{
Action: basics.DeleteAction,
}
- err = cx.Ledger.DelLocal(addr, key, accountIdx)
+ err = cx.Ledger.DelLocal(addr, cx.appID, key, accountIdx)
}
if err != nil {
cx.err = err
@@ -3599,7 +3638,7 @@ func opAppGlobalDel(cx *EvalContext) {
cx.Txn.EvalDelta.GlobalDelta[key] = basics.ValueDelta{
Action: basics.DeleteAction,
}
- err := cx.Ledger.DelGlobal(key)
+ err := cx.Ledger.DelGlobal(cx.appID, key)
if err != nil {
cx.err = err
return
@@ -3617,7 +3656,7 @@ func opAppGlobalDel(cx *EvalContext) {
func appReference(cx *EvalContext, ref uint64, foreign bool) (basics.AppIndex, error) {
if cx.version >= directRefEnabledVersion {
if ref == 0 {
- return cx.Ledger.ApplicationID(), nil
+ return cx.appID, nil
}
if ref <= uint64(len(cx.Txn.Txn.ForeignApps)) {
return basics.AppIndex(cx.Txn.Txn.ForeignApps[ref-1]), nil
@@ -3627,22 +3666,21 @@ func appReference(cx *EvalContext, ref uint64, foreign bool) (basics.AppIndex, e
return appID, nil
}
}
- // It should be legal to use your own app id, which
- // can't be in ForeignApps during creation, because it
- // is unknown then. But it can be discovered in the
- // app code. It's tempting to combine this with the
- // == 0 test, above, but it must come after the check
- // for being below len(ForeignApps)
- if ref == uint64(cx.Ledger.ApplicationID()) {
- return cx.Ledger.ApplicationID(), nil
+ // It should be legal to use your own app id, which can't be in
+ // ForeignApps during creation, because it is unknown then. But it can
+ // be discovered in the app code. It's tempting to combine this with
+ // the == 0 test, above, but it must come after the check for being
+ // below len(ForeignApps)
+ if ref == uint64(cx.appID) {
+ return cx.appID, nil
}
} else {
// Old rules
+ if ref == 0 {
+ return cx.appID, nil
+ }
if foreign {
// In old versions, a foreign reference must be an index in ForeignAssets or 0
- if ref == 0 {
- return cx.Ledger.ApplicationID(), nil
- }
if ref <= uint64(len(cx.Txn.Txn.ForeignApps)) {
return basics.AppIndex(cx.Txn.Txn.ForeignApps[ref-1]), nil
}
@@ -3660,6 +3698,9 @@ func asaReference(cx *EvalContext, ref uint64, foreign bool) (basics.AssetIndex,
if ref < uint64(len(cx.Txn.Txn.ForeignAssets)) {
return basics.AssetIndex(cx.Txn.Txn.ForeignAssets[ref]), nil
}
+ if ref >= cx.initialCounter {
+ return basics.AssetIndex(ref), nil
+ }
for _, assetID := range cx.Txn.Txn.ForeignAssets {
if assetID == basics.AssetIndex(ref) {
return assetID, nil
@@ -3826,33 +3867,27 @@ func opLog(cx *EvalContext) {
}
func authorizedSender(cx *EvalContext, addr basics.Address) bool {
- appAddr, err := cx.getApplicationAddress()
- if err != nil {
- return false
- }
authorizer, err := cx.Ledger.Authorizer(addr)
if err != nil {
return false
}
- return appAddr == authorizer
+ return cx.getApplicationAddress() == authorizer
}
// addInnerTxn appends a fresh SignedTxn to subtxns, populated with reasonable
// defaults.
func addInnerTxn(cx *EvalContext) error {
- addr, err := cx.getApplicationAddress()
- if err != nil {
- return err
- }
+ addr := cx.getApplicationAddress()
// For compatibility with v5, in which failures only occurred in the submit,
- // we only fail here if we are OVER the MaxInnerTransactions limit. Thus
- // this allows construction of one more Inner than is actually allowed, and
- // will fail in submit. (But we do want the check here, so this can't become
- // unbounded.) The MaxTxGroupSize check can be, and is, precise.
- if len(cx.Txn.EvalDelta.InnerTxns)+len(cx.subtxns) > cx.Proto.MaxInnerTransactions ||
- len(cx.subtxns) >= cx.Proto.MaxTxGroupSize {
- return errors.New("attempt to create too many inner transactions")
+ // we only fail here if we are already over the max inner limit. Thus this
+ // allows construction of one more Inner than is actually allowed, and will
+ // fail in submit. (But we do want the check here, so this can't become
+ // unbounded.) The MaxTxGroupSize check can be, and is, precise. (That is,
+ // if we are at max group size, we can panic now, since we are trying to add
+ // too many)
+ if len(cx.Txn.EvalDelta.InnerTxns)+len(cx.subtxns) > cx.allowedInners() || len(cx.subtxns) >= cx.Proto.MaxTxGroupSize {
+ return fmt.Errorf("too many inner transactions %d", len(cx.Txn.EvalDelta.InnerTxns)+len(cx.subtxns))
}
stxn := transactions.SignedTxn{}
@@ -3921,6 +3956,9 @@ func (cx *EvalContext) availableAsset(sv stackValue) (basics.AssetIndex, error)
if err != nil {
return basics.AssetIndex(0), err
}
+ if aid >= cx.initialCounter {
+ return basics.AssetIndex(aid), nil
+ }
// Ensure that aid is in Foreign Assets
for _, assetID := range cx.Txn.Txn.ForeignAssets {
if assetID == basics.AssetIndex(aid) {
@@ -3946,7 +3984,7 @@ func (cx *EvalContext) availableApp(sv stackValue) (basics.AppIndex, error) {
}
}
// Or, it can be the current app
- if cx.Ledger.ApplicationID() == aid {
+ if cx.appID == aid {
return aid, nil
}
@@ -4207,10 +4245,11 @@ func opTxSubmit(cx *EvalContext) {
return
}
- // Should never trigger, since itxn_next checks these too.
- if len(cx.Txn.EvalDelta.InnerTxns)+len(cx.subtxns) > cx.Proto.MaxInnerTransactions ||
- len(cx.subtxns) > cx.Proto.MaxTxGroupSize {
- cx.err = errors.New("too many inner transactions")
+ // Should rarely trigger, since itxn_next checks these too. (but that check
+ // must be imperfect, see its comment) In contrast to that check, subtxns is
+ // already populated here.
+ if len(cx.Txn.EvalDelta.InnerTxns)+len(cx.subtxns) > cx.allowedInners() || len(cx.subtxns) > cx.Proto.MaxTxGroupSize {
+ cx.err = fmt.Errorf("too many inner transactions %d", len(cx.Txn.EvalDelta.InnerTxns)+len(cx.subtxns))
return
}
@@ -4241,8 +4280,6 @@ func opTxSubmit(cx *EvalContext) {
*cx.FeeCredit = basics.AddSaturate(*cx.FeeCredit, overpay)
}
- cx.appStack = append(cx.appStack, cx.Ledger.ApplicationID()) // push app
-
// All subtxns will have zero'd GroupID since GroupID can't be set in
// AVM. (no need to blank it out before hashing for TxID)
var group transactions.TxGroup
@@ -4270,9 +4307,13 @@ func opTxSubmit(cx *EvalContext) {
// Disallow re-entrancy
if cx.subtxns[itx].Txn.Type == protocol.ApplicationCallTx {
- for _, aid := range cx.appStack {
- if aid == cx.subtxns[itx].Txn.ApplicationID {
- cx.err = fmt.Errorf("attempt to re-enter %d", aid)
+ if cx.appID == cx.subtxns[itx].Txn.ApplicationID {
+ cx.err = fmt.Errorf("attempt to self-call")
+ return
+ }
+ for parent := cx.caller; parent != nil; parent = parent.caller {
+ if parent.appID == cx.subtxns[itx].Txn.ApplicationID {
+ cx.err = fmt.Errorf("attempt to re-enter %d", parent.appID)
return
}
}
@@ -4292,7 +4333,7 @@ func opTxSubmit(cx *EvalContext) {
}
}
- ep := NewAppEvalParams(transactions.WrapSignedTxnsWithAD(cx.subtxns), cx.Proto, cx.Specials, cx)
+ ep := NewInnerEvalParams(cx.subtxns, cx)
for i := range ep.TxnGroup {
err := cx.Ledger.Perform(i, ep)
if err != nil {
@@ -4300,7 +4341,6 @@ func opTxSubmit(cx *EvalContext) {
return
}
}
- cx.appStack = cx.appStack[:len(cx.appStack)-1] // pop app
cx.Txn.EvalDelta.InnerTxns = append(cx.Txn.EvalDelta.InnerTxns, ep.TxnGroup...)
cx.subtxns = nil
}
@@ -4332,3 +4372,28 @@ func (cx *EvalContext) PcDetails() (pc int, dis string) {
}
return cx.pc, dis
}
+
+func base64Decode(encoded []byte, encoding *base64.Encoding) ([]byte, error) {
+ decoded := make([]byte, encoding.DecodedLen(len(encoded)))
+ n, err := encoding.Decode(decoded, encoded)
+ if err != nil {
+ return decoded[:0], err
+ }
+ return decoded[:n], err
+}
+
+func opBase64Decode(cx *EvalContext) {
+ last := len(cx.stack) - 1
+ alphabetField := Base64Alphabet(cx.program[cx.pc+1])
+ fs, ok := base64AlphabetSpecByField[alphabetField]
+ if !ok || fs.version > cx.version {
+ cx.err = fmt.Errorf("invalid base64_decode alphabet %d", alphabetField)
+ return
+ }
+
+ encoding := base64.URLEncoding
+ if alphabetField == StdAlph {
+ encoding = base64.StdEncoding
+ }
+ cx.stack[last].Bytes, cx.err = base64Decode(cx.stack[last].Bytes, encoding)
+}
diff --git a/data/transactions/logic/evalAppTxn_test.go b/data/transactions/logic/evalAppTxn_test.go
index 69bb35c9e9..13f7759c45 100644
--- a/data/transactions/logic/evalAppTxn_test.go
+++ b/data/transactions/logic/evalAppTxn_test.go
@@ -14,7 +14,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with go-algorand. If not, see .
-package logic
+package logic_test
import (
"encoding/hex"
@@ -23,89 +23,97 @@ import (
"testing"
"github.com/algorand/go-algorand/data/basics"
+ "github.com/algorand/go-algorand/data/transactions"
+ . "github.com/algorand/go-algorand/data/transactions/logic"
+ "github.com/algorand/go-algorand/data/txntest"
+ "github.com/algorand/go-algorand/protocol"
"github.com/stretchr/testify/require"
)
func TestInnerTypesV5(t *testing.T) {
- v5, _, _ := makeSampleEnvWithVersion(5)
+ v5, _, _ := MakeSampleEnvWithVersion(5)
// not alllowed in v5
- testApp(t, "itxn_begin; byte \"keyreg\"; itxn_field Type; itxn_submit; int 1;", v5, "keyreg is not a valid Type for itxn_field")
- testApp(t, "itxn_begin; int keyreg; itxn_field TypeEnum; itxn_submit; int 1;", v5, "keyreg is not a valid Type for itxn_field")
+ TestApp(t, "itxn_begin; byte \"keyreg\"; itxn_field Type; itxn_submit; int 1;", v5, "keyreg is not a valid Type for itxn_field")
+ TestApp(t, "itxn_begin; int keyreg; itxn_field TypeEnum; itxn_submit; int 1;", v5, "keyreg is not a valid Type for itxn_field")
- testApp(t, "itxn_begin; byte \"appl\"; itxn_field Type; itxn_submit; int 1;", v5, "appl is not a valid Type for itxn_field")
- testApp(t, "itxn_begin; int appl; itxn_field TypeEnum; itxn_submit; int 1;", v5, "appl is not a valid Type for itxn_field")
+ TestApp(t, "itxn_begin; byte \"appl\"; itxn_field Type; itxn_submit; int 1;", v5, "appl is not a valid Type for itxn_field")
+ TestApp(t, "itxn_begin; int appl; itxn_field TypeEnum; itxn_submit; int 1;", v5, "appl is not a valid Type for itxn_field")
}
func TestCurrentInnerTypes(t *testing.T) {
- ep, tx, ledger := makeSampleEnv()
- testApp(t, "itxn_submit; int 1;", ep, "itxn_submit without itxn_begin")
- testApp(t, "int pay; itxn_field TypeEnum; itxn_submit; int 1;", ep, "itxn_field without itxn_begin")
- testApp(t, "itxn_begin; itxn_submit; int 1;", ep, "unknown tx type")
+ ep, tx, ledger := MakeSampleEnv()
+ TestApp(t, "itxn_submit; int 1;", ep, "itxn_submit without itxn_begin")
+ TestApp(t, "int pay; itxn_field TypeEnum; itxn_submit; int 1;", ep, "itxn_field without itxn_begin")
+ TestApp(t, "itxn_begin; itxn_submit; int 1;", ep, "unknown tx type")
// bad type
- testApp(t, "itxn_begin; byte \"pya\"; itxn_field Type; itxn_submit; int 1;", ep, "pya is not a valid Type")
+ TestApp(t, "itxn_begin; byte \"pya\"; itxn_field Type; itxn_submit; int 1;", ep, "pya is not a valid Type")
// mixed up the int form for the byte form
- testApp(t, obfuscate("itxn_begin; int pay; itxn_field Type; itxn_submit; int 1;"), ep, "Type arg not a byte array")
+ TestApp(t, Obfuscate("itxn_begin; int pay; itxn_field Type; itxn_submit; int 1;"), ep, "Type arg not a byte array")
// or vice versa
- testApp(t, obfuscate("itxn_begin; byte \"pay\"; itxn_field TypeEnum; itxn_submit; int 1;"), ep, "not a uint64")
+ TestApp(t, Obfuscate("itxn_begin; byte \"pay\"; itxn_field TypeEnum; itxn_submit; int 1;"), ep, "not a uint64")
// some bad types
- testApp(t, "itxn_begin; int 42; itxn_field TypeEnum; itxn_submit; int 1;", ep, "42 is not a valid TypeEnum")
- testApp(t, "itxn_begin; int 0; itxn_field TypeEnum; itxn_submit; int 1;", ep, "0 is not a valid TypeEnum")
+ TestApp(t, "itxn_begin; int 42; itxn_field TypeEnum; itxn_submit; int 1;", ep, "42 is not a valid TypeEnum")
+ TestApp(t, "itxn_begin; int 0; itxn_field TypeEnum; itxn_submit; int 1;", ep, "0 is not a valid TypeEnum")
// "insufficient balance" because app account is charged fee
// (defaults make these 0 pay|axfer to zero address, from app account)
- testApp(t, "itxn_begin; byte \"pay\"; itxn_field Type; itxn_submit; int 1;", ep, "insufficient balance")
- testApp(t, "itxn_begin; byte \"axfer\"; itxn_field Type; itxn_submit; int 1;", ep, "insufficient balance")
- testApp(t, "itxn_begin; int pay; itxn_field TypeEnum; itxn_submit; int 1;", ep, "insufficient balance")
- testApp(t, "itxn_begin; int axfer; itxn_field TypeEnum; itxn_submit; int 1;", ep, "insufficient balance")
+ TestApp(t, "itxn_begin; byte \"pay\"; itxn_field Type; itxn_submit; int 1;", ep, "insufficient balance")
+ TestApp(t, "itxn_begin; byte \"axfer\"; itxn_field Type; itxn_submit; int 1;", ep, "insufficient balance")
+ TestApp(t, "itxn_begin; int pay; itxn_field TypeEnum; itxn_submit; int 1;", ep, "insufficient balance")
+ TestApp(t, "itxn_begin; int axfer; itxn_field TypeEnum; itxn_submit; int 1;", ep, "insufficient balance")
- testApp(t, "itxn_begin; byte \"acfg\"; itxn_field Type; itxn_submit; int 1;", ep, "insufficient balance")
- testApp(t, "itxn_begin; byte \"afrz\"; itxn_field Type; itxn_submit; int 1;", ep, "insufficient balance")
- testApp(t, "itxn_begin; int acfg; itxn_field TypeEnum; itxn_submit; int 1;", ep, "insufficient balance")
- testApp(t, "itxn_begin; int afrz; itxn_field TypeEnum; itxn_submit; int 1;", ep, "insufficient balance")
+ TestApp(t, "itxn_begin; byte \"acfg\"; itxn_field Type; itxn_submit; int 1;", ep, "insufficient balance")
+ TestApp(t, "itxn_begin; byte \"afrz\"; itxn_field Type; itxn_submit; int 1;", ep, "insufficient balance")
+ TestApp(t, "itxn_begin; int acfg; itxn_field TypeEnum; itxn_submit; int 1;", ep, "insufficient balance")
+ TestApp(t, "itxn_begin; int afrz; itxn_field TypeEnum; itxn_submit; int 1;", ep, "insufficient balance")
// allowed since v6
- testApp(t, "itxn_begin; byte \"keyreg\"; itxn_field Type; itxn_submit; int 1;", ep, "insufficient balance")
- testApp(t, "itxn_begin; int keyreg; itxn_field TypeEnum; itxn_submit; int 1;", ep, "insufficient balance")
- testApp(t, "itxn_begin; byte \"appl\"; itxn_field Type; itxn_submit; int 1;", ep, "insufficient balance")
- testApp(t, "itxn_begin; int appl; itxn_field TypeEnum; itxn_submit; int 1;", ep, "insufficient balance")
+ TestApp(t, "itxn_begin; byte \"keyreg\"; itxn_field Type; itxn_submit; int 1;", ep, "insufficient balance")
+ TestApp(t, "itxn_begin; int keyreg; itxn_field TypeEnum; itxn_submit; int 1;", ep, "insufficient balance")
+ TestApp(t, "itxn_begin; byte \"appl\"; itxn_field Type; itxn_submit; int 1;", ep, "insufficient balance")
+ TestApp(t, "itxn_begin; int appl; itxn_field TypeEnum; itxn_submit; int 1;", ep, "insufficient balance")
// Establish 888 as the app id, and fund it.
ledger.NewApp(tx.Receiver, 888, basics.AppParams{})
ledger.NewAccount(basics.AppIndex(888).Address(), 200000)
- testApp(t, "itxn_begin; byte \"pay\"; itxn_field Type; itxn_submit; int 1;", ep)
- testApp(t, "itxn_begin; int pay; itxn_field TypeEnum; itxn_submit; int 1;", ep)
+ TestApp(t, "itxn_begin; byte \"pay\"; itxn_field Type; itxn_submit; int 1;", ep)
+ TestApp(t, "itxn_begin; int pay; itxn_field TypeEnum; itxn_submit; int 1;", ep)
// Can't submit because we haven't finished setup, but type passes itxn_field
- testApp(t, "itxn_begin; byte \"axfer\"; itxn_field Type; int 1;", ep)
- testApp(t, "itxn_begin; int axfer; itxn_field TypeEnum; int 1;", ep)
- testApp(t, "itxn_begin; byte \"acfg\"; itxn_field Type; int 1;", ep)
- testApp(t, "itxn_begin; int acfg; itxn_field TypeEnum; int 1;", ep)
- testApp(t, "itxn_begin; byte \"afrz\"; itxn_field Type; int 1;", ep)
- testApp(t, "itxn_begin; int afrz; itxn_field TypeEnum; int 1;", ep)
+ TestApp(t, "itxn_begin; byte \"axfer\"; itxn_field Type; int 1;", ep)
+ TestApp(t, "itxn_begin; int axfer; itxn_field TypeEnum; int 1;", ep)
+ TestApp(t, "itxn_begin; byte \"acfg\"; itxn_field Type; int 1;", ep)
+ TestApp(t, "itxn_begin; int acfg; itxn_field TypeEnum; int 1;", ep)
+ TestApp(t, "itxn_begin; byte \"afrz\"; itxn_field Type; int 1;", ep)
+ TestApp(t, "itxn_begin; int afrz; itxn_field TypeEnum; int 1;", ep)
}
func TestFieldTypes(t *testing.T) {
- ep, _, _ := makeSampleEnv()
- testApp(t, "itxn_begin; byte \"pay\"; itxn_field Sender;", ep, "not an address")
- testApp(t, obfuscate("itxn_begin; int 7; itxn_field Receiver;"), ep, "not an address")
- testApp(t, "itxn_begin; byte \"\"; itxn_field CloseRemainderTo;", ep, "not an address")
- testApp(t, "itxn_begin; byte \"\"; itxn_field AssetSender;", ep, "not an address")
+ ep, _, _ := MakeSampleEnv()
+ TestApp(t, "itxn_begin; byte \"pay\"; itxn_field Sender;", ep, "not an address")
+ TestApp(t, Obfuscate("itxn_begin; int 7; itxn_field Receiver;"), ep, "not an address")
+ TestApp(t, "itxn_begin; byte \"\"; itxn_field CloseRemainderTo;", ep, "not an address")
+ TestApp(t, "itxn_begin; byte \"\"; itxn_field AssetSender;", ep, "not an address")
// can't really tell if it's an addres, so 32 bytes gets further
- testApp(t, "itxn_begin; byte \"01234567890123456789012345678901\"; itxn_field AssetReceiver;",
+ TestApp(t, "itxn_begin; byte \"01234567890123456789012345678901\"; itxn_field AssetReceiver;",
ep, "invalid Account reference")
// but a b32 string rep is not an account
- testApp(t, "itxn_begin; byte \"GAYTEMZUGU3DOOBZGAYTEMZUGU3DOOBZGAYTEMZUGU3DOOBZGAYZIZD42E\"; itxn_field AssetCloseTo;",
+ TestApp(t, "itxn_begin; byte \"GAYTEMZUGU3DOOBZGAYTEMZUGU3DOOBZGAYTEMZUGU3DOOBZGAYZIZD42E\"; itxn_field AssetCloseTo;",
ep, "not an address")
- testApp(t, obfuscate("itxn_begin; byte \"pay\"; itxn_field Fee;"), ep, "not a uint64")
- testApp(t, obfuscate("itxn_begin; byte 0x01; itxn_field Amount;"), ep, "not a uint64")
- testApp(t, obfuscate("itxn_begin; byte 0x01; itxn_field XferAsset;"), ep, "not a uint64")
- testApp(t, obfuscate("itxn_begin; byte 0x01; itxn_field AssetAmount;"), ep, "not a uint64")
+ TestApp(t, Obfuscate("itxn_begin; byte \"pay\"; itxn_field Fee;"), ep, "not a uint64")
+ TestApp(t, Obfuscate("itxn_begin; byte 0x01; itxn_field Amount;"), ep, "not a uint64")
+ TestApp(t, Obfuscate("itxn_begin; byte 0x01; itxn_field XferAsset;"), ep, "not a uint64")
+ TestApp(t, Obfuscate("itxn_begin; byte 0x01; itxn_field AssetAmount;"), ep, "not a uint64")
}
+func appAddr(id int) basics.Address {
+ return basics.AppIndex(id).Address()
+}
+
func TestAppPay(t *testing.T) {
pay := `
itxn_begin
@@ -118,25 +126,25 @@ func TestAppPay(t *testing.T) {
int 1
`
- ep, tx, ledger := makeSampleEnv()
+ ep, tx, ledger := MakeSampleEnv()
ledger.NewApp(tx.Receiver, 888, basics.AppParams{})
- testApp(t, "txn Sender; balance; int 0; ==;", ep)
- testApp(t, "txn Sender; txn Accounts 1; int 100"+pay, ep, "unauthorized")
- testApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100"+pay, ep,
+ TestApp(t, "txn Sender; balance; int 0; ==;", ep)
+ TestApp(t, "txn Sender; txn Accounts 1; int 100"+pay, ep, "unauthorized")
+ TestApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100"+pay, ep,
"insufficient balance")
- ledger.NewAccount(ledger.ApplicationID().Address(), 1000000)
+ ledger.NewAccount(appAddr(888), 1000000)
- // You might expect this to fail because of min balance issue
+ // You might NewExpect this to fail because of min balance issue
// (receiving account only gets 100 microalgos). It does not fail at
// this level, instead, we must be certain that the existing min
// balance check in eval.transaction() properly notices and fails
// the transaction later. This fits with the model that we check
// min balances once at the end of each "top-level" transaction.
- testApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100"+pay, ep)
+ TestApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100"+pay, ep)
// 100 of 1000000 spent, plus MinTxnFee in our fake protocol is 1001
- testApp(t, "global CurrentApplicationAddress; balance; int 998899; ==", ep)
- testApp(t, "txn Receiver; balance; int 100; ==", ep)
+ TestApp(t, "global CurrentApplicationAddress; balance; int 998899; ==", ep)
+ TestApp(t, "txn Receiver; balance; int 100; ==", ep)
close := `
itxn_begin
@@ -145,14 +153,14 @@ func TestAppPay(t *testing.T) {
itxn_submit
int 1
`
- testApp(t, close, ep)
- testApp(t, "global CurrentApplicationAddress; balance; !", ep)
+ TestApp(t, close, ep)
+ TestApp(t, "global CurrentApplicationAddress; balance; !", ep)
// Receiver got most of the algos (except 1001 for fee)
- testApp(t, "txn Receiver; balance; int 997998; ==", ep)
+ TestApp(t, "txn Receiver; balance; int 997998; ==", ep)
}
func TestAppAssetOptIn(t *testing.T) {
- ep, tx, ledger := makeSampleEnv()
+ ep, tx, ledger := MakeSampleEnv()
// Establish 888 as the app id, and fund it.
ledger.NewApp(tx.Receiver, 888, basics.AppParams{})
ledger.NewAccount(basics.AppIndex(888).Address(), 200000)
@@ -166,9 +174,9 @@ txn Sender; itxn_field AssetReceiver;
itxn_submit
int 1
`
- testApp(t, axfer, ep, "invalid Asset reference")
+ TestApp(t, axfer, ep, "invalid Asset reference")
tx.ForeignAssets = append(tx.ForeignAssets, 25)
- testApp(t, axfer, ep, "not opted in") // app account not opted in
+ TestApp(t, axfer, ep, "not opted in") // app account not opted in
optin := `
itxn_begin
int axfer; itxn_field TypeEnum;
@@ -178,23 +186,23 @@ global CurrentApplicationAddress; itxn_field AssetReceiver;
itxn_submit
int 1
`
- testApp(t, optin, ep, "does not exist")
+ TestApp(t, optin, ep, "does not exist")
// Asset 25
ledger.NewAsset(tx.Sender, 25, basics.AssetParams{
Total: 10,
UnitName: "x",
AssetName: "Cross",
})
- testApp(t, optin, ep)
+ TestApp(t, optin, ep)
- testApp(t, axfer, ep, "insufficient balance") // opted in, but balance=0
+ TestApp(t, axfer, ep, "insufficient balance") // opted in, but balance=0
// Fund the app account with the asset
ledger.NewHolding(basics.AppIndex(888).Address(), 25, 5, false)
- testApp(t, axfer, ep)
- testApp(t, axfer, ep)
- testApp(t, axfer, ep, "insufficient balance") // balance = 1, tried to move 2)
- testApp(t, "global CurrentApplicationAddress; int 25; asset_holding_get AssetBalance; assert; int 1; ==", ep)
+ TestApp(t, axfer, ep)
+ TestApp(t, axfer, ep)
+ TestApp(t, axfer, ep, "insufficient balance") // balance = 1, tried to move 2)
+ TestApp(t, "global CurrentApplicationAddress; int 25; asset_holding_get AssetBalance; assert; int 1; ==", ep)
close := `
itxn_begin
@@ -206,8 +214,8 @@ txn Sender; itxn_field AssetCloseTo;
itxn_submit
int 1
`
- testApp(t, close, ep)
- testApp(t, "global CurrentApplicationAddress; int 25; asset_holding_get AssetBalance; !; assert; !", ep)
+ TestApp(t, close, ep)
+ TestApp(t, "global CurrentApplicationAddress; int 25; asset_holding_get AssetBalance; !; assert; !", ep)
}
func TestRekeyPay(t *testing.T) {
@@ -221,13 +229,13 @@ func TestRekeyPay(t *testing.T) {
itxn_submit
`
- ep, tx, ledger := makeSampleEnv()
+ ep, tx, ledger := MakeSampleEnv()
ledger.NewApp(tx.Receiver, 888, basics.AppParams{})
- testApp(t, "txn Sender; balance; int 0; ==;", ep)
- testApp(t, "txn Sender; txn Accounts 1; int 100"+pay, ep, "unauthorized")
+ TestApp(t, "txn Sender; balance; int 0; ==;", ep)
+ TestApp(t, "txn Sender; txn Accounts 1; int 100"+pay, ep, "unauthorized")
ledger.NewAccount(tx.Sender, 120+ep.Proto.MinTxnFee)
ledger.Rekey(tx.Sender, basics.AppIndex(888).Address())
- testApp(t, "txn Sender; txn Accounts 1; int 100"+pay+"; int 1", ep)
+ TestApp(t, "txn Sender; txn Accounts 1; int 100"+pay+"; int 1", ep)
// Note that the Sender would fail min balance check if we did it here.
// It seems proper to wait until end of txn though.
// See explanation in logicLedger's Perform()
@@ -246,15 +254,15 @@ func TestRekeyBack(t *testing.T) {
itxn_submit
`
- ep, tx, ledger := makeSampleEnv()
+ ep, tx, ledger := MakeSampleEnv()
ledger.NewApp(tx.Receiver, 888, basics.AppParams{})
- testApp(t, "txn Sender; balance; int 0; ==;", ep)
- testApp(t, "txn Sender; txn Accounts 1; int 100"+payAndUnkey, ep, "unauthorized")
+ TestApp(t, "txn Sender; balance; int 0; ==;", ep)
+ TestApp(t, "txn Sender; txn Accounts 1; int 100"+payAndUnkey, ep, "unauthorized")
ledger.NewAccount(tx.Sender, 120+3*ep.Proto.MinTxnFee)
ledger.Rekey(tx.Sender, basics.AppIndex(888).Address())
- testApp(t, "txn Sender; txn Accounts 1; int 100"+payAndUnkey+"; int 1", ep)
+ TestApp(t, "txn Sender; txn Accounts 1; int 100"+payAndUnkey+"; int 1", ep)
// now rekeyed back to original
- testApp(t, "txn Sender; txn Accounts 1; int 100"+payAndUnkey, ep, "unauthorized")
+ TestApp(t, "txn Sender; txn Accounts 1; int 100"+payAndUnkey, ep, "unauthorized")
}
func TestDefaultSender(t *testing.T) {
@@ -267,13 +275,13 @@ func TestDefaultSender(t *testing.T) {
itxn_submit
`
- ep, tx, ledger := makeSampleEnv()
+ ep, tx, ledger := MakeSampleEnv()
ledger.NewApp(tx.Receiver, 888, basics.AppParams{})
- tx.Accounts = append(tx.Accounts, ledger.ApplicationID().Address())
- testApp(t, "txn Accounts 1; int 100"+pay, ep, "insufficient balance")
- ledger.NewAccount(ledger.ApplicationID().Address(), 1000000)
- testApp(t, "txn Accounts 1; int 100"+pay+"int 1", ep)
- testApp(t, "global CurrentApplicationAddress; balance; int 998899; ==", ep)
+ tx.Accounts = append(tx.Accounts, appAddr(888))
+ TestApp(t, "txn Accounts 1; int 100"+pay, ep, "insufficient balance")
+ ledger.NewAccount(appAddr(888), 1000000)
+ TestApp(t, "txn Accounts 1; int 100"+pay+"int 1", ep)
+ TestApp(t, "global CurrentApplicationAddress; balance; int 998899; ==", ep)
}
func TestAppAxfer(t *testing.T) {
@@ -289,32 +297,32 @@ func TestAppAxfer(t *testing.T) {
itxn_submit
`
- ep, tx, ledger := makeSampleEnv()
+ ep, tx, ledger := MakeSampleEnv()
ledger.NewApp(tx.Receiver, 888, basics.AppParams{})
ledger.NewAsset(tx.Receiver, 777, basics.AssetParams{}) // not in foreign-assets of sample
ledger.NewAsset(tx.Receiver, 77, basics.AssetParams{}) // in foreign-assets of sample
- testApp(t, "txn Sender; int 777; asset_holding_get AssetBalance; assert; int 0; ==;", ep,
+ TestApp(t, "txn Sender; int 777; asset_holding_get AssetBalance; assert; int 0; ==;", ep,
"invalid Asset reference") // 777 not in foreign-assets
- testApp(t, "txn Sender; int 77; asset_holding_get AssetBalance; assert; int 0; ==;", ep,
+ TestApp(t, "txn Sender; int 77; asset_holding_get AssetBalance; assert; int 0; ==;", ep,
"assert failed") // because Sender not opted-in
- testApp(t, "global CurrentApplicationAddress; int 77; asset_holding_get AssetBalance; assert; int 0; ==;", ep,
+ TestApp(t, "global CurrentApplicationAddress; int 77; asset_holding_get AssetBalance; assert; int 0; ==;", ep,
"assert failed") // app account not opted in
- ledger.NewAccount(ledger.ApplicationID().Address(), 10000) // plenty for fees
- ledger.NewHolding(ledger.ApplicationID().Address(), 77, 3000, false)
- testApp(t, "global CurrentApplicationAddress; int 77; asset_holding_get AssetBalance; assert; int 3000; ==;", ep)
+ ledger.NewAccount(appAddr(888), 10000) // plenty for fees
+ ledger.NewHolding(appAddr(888), 77, 3000, false)
+ TestApp(t, "global CurrentApplicationAddress; int 77; asset_holding_get AssetBalance; assert; int 3000; ==;", ep)
- testApp(t, "txn Sender; txn Accounts 1; int 100"+axfer, ep, "unauthorized")
- testApp(t, "global CurrentApplicationAddress; txn Accounts 0; int 100"+axfer, ep,
+ TestApp(t, "txn Sender; txn Accounts 1; int 100"+axfer, ep, "unauthorized")
+ TestApp(t, "global CurrentApplicationAddress; txn Accounts 0; int 100"+axfer, ep,
fmt.Sprintf("Receiver (%s) not opted in", tx.Sender)) // txn.Sender (receiver of the axfer) isn't opted in
- testApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100000"+axfer, ep,
+ TestApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100000"+axfer, ep,
"insufficient balance")
// Temporarily remove from ForeignAssets to ensure App Account
// doesn't get some sort of free pass to send arbitrary assets.
save := tx.ForeignAssets
tx.ForeignAssets = []basics.AssetIndex{6, 10}
- testApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100000"+axfer, ep,
+ TestApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100000"+axfer, ep,
"invalid Asset reference 77")
tx.ForeignAssets = save
@@ -327,14 +335,14 @@ func TestAppAxfer(t *testing.T) {
itxn_field TypeEnum
itxn_submit
`
- testApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100"+noid+"int 1", ep,
- fmt.Sprintf("Sender (%s) not opted in to 0", ledger.ApplicationID().Address()))
+ TestApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100"+noid+"int 1", ep,
+ fmt.Sprintf("Sender (%s) not opted in to 0", appAddr(888)))
- testApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100"+axfer+"int 1", ep)
+ TestApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100"+axfer+"int 1", ep)
// 100 of 3000 spent
- testApp(t, "global CurrentApplicationAddress; int 77; asset_holding_get AssetBalance; assert; int 2900; ==", ep)
- testApp(t, "txn Accounts 1; int 77; asset_holding_get AssetBalance; assert; int 100; ==", ep)
+ TestApp(t, "global CurrentApplicationAddress; int 77; asset_holding_get AssetBalance; assert; int 2900; ==", ep)
+ TestApp(t, "txn Accounts 1; int 77; asset_holding_get AssetBalance; assert; int 100; ==", ep)
}
func TestExtraFields(t *testing.T) {
@@ -349,11 +357,11 @@ func TestExtraFields(t *testing.T) {
itxn_submit
`
- ep, tx, ledger := makeSampleEnv()
+ ep, tx, ledger := MakeSampleEnv()
ledger.NewApp(tx.Receiver, 888, basics.AppParams{})
- testApp(t, "txn Sender; balance; int 0; ==;", ep)
- testApp(t, "txn Sender; txn Accounts 1; int 100"+pay, ep, "unauthorized")
- testApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100"+pay, ep,
+ TestApp(t, "txn Sender; balance; int 0; ==;", ep)
+ TestApp(t, "txn Sender; txn Accounts 1; int 100"+pay, ep, "unauthorized")
+ TestApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100"+pay, ep,
"non-zero fields for type axfer")
}
@@ -371,12 +379,12 @@ func TestBadFieldV5(t *testing.T) {
itxn_submit
`
- ep, tx, ledger := makeSampleEnvWithVersion(5)
+ ep, tx, ledger := MakeSampleEnvWithVersion(5)
ledger.NewApp(tx.Receiver, 888, basics.AppParams{})
// Assemble a good program, then change the field to a bad one
- ops := testProg(t, "global CurrentApplicationAddress; txn Accounts 1; int 100"+pay, 5)
+ ops := TestProg(t, "global CurrentApplicationAddress; txn Accounts 1; int 100"+pay, 5)
ops.Program[len(ops.Program)-2] = byte(RekeyTo)
- testAppBytes(t, ops.Program, ep, "invalid itxn_field RekeyTo")
+ TestAppBytes(t, ops.Program, ep, "invalid itxn_field RekeyTo")
}
func TestBadField(t *testing.T) {
@@ -395,14 +403,14 @@ func TestBadField(t *testing.T) {
itxn_submit
`
- ep, tx, ledger := makeSampleEnv()
+ ep, tx, ledger := MakeSampleEnv()
ledger.NewApp(tx.Receiver, 888, basics.AppParams{})
- ops := testProg(t, "global CurrentApplicationAddress; txn Accounts 1; int 100"+pay, AssemblerMaxVersion)
+ ops := TestProg(t, "global CurrentApplicationAddress; txn Accounts 1; int 100"+pay, AssemblerMaxVersion)
ops.Program[len(ops.Program)-2] = byte(FirstValid)
- testAppBytes(t, ops.Program, ep, "invalid itxn_field FirstValid")
+ TestAppBytes(t, ops.Program, ep, "invalid itxn_field FirstValid")
}
-func TestNumInner(t *testing.T) {
+func TestNumInnerShallow(t *testing.T) {
pay := `
itxn_begin
int 1
@@ -414,15 +422,65 @@ func TestNumInner(t *testing.T) {
itxn_submit
`
- ep, tx, ledger := makeSampleEnv()
+ ep, tx, ledger := MakeSampleEnv()
ledger.NewApp(tx.Receiver, 888, basics.AppParams{})
- ledger.NewAccount(ledger.ApplicationID().Address(), 1000000)
- testApp(t, pay+";int 1", ep)
- testApp(t, pay+pay+";int 1", ep)
- testApp(t, pay+pay+pay+";int 1", ep)
- testApp(t, pay+pay+pay+pay+";int 1", ep)
+ ledger.NewAccount(appAddr(888), 1000000)
+ TestApp(t, pay+";int 1", ep)
+ TestApp(t, pay+pay+";int 1", ep)
+ TestApp(t, pay+pay+pay+";int 1", ep)
+ TestApp(t, pay+pay+pay+pay+";int 1", ep)
// In the sample proto, MaxInnerTransactions = 4
- testApp(t, pay+pay+pay+pay+pay+";int 1", ep, "too many inner transactions")
+ TestApp(t, pay+pay+pay+pay+pay+";int 1", ep, "too many inner transactions")
+}
+
+// TestNumInnerPooled ensures that inner call limits are pooled across app calls
+// in a group.
+func TestNumInnerPooled(t *testing.T) {
+ pay := `
+ itxn_begin
+ int 1
+ itxn_field Amount
+ txn Accounts 0
+ itxn_field Receiver
+ int pay
+ itxn_field TypeEnum
+ itxn_submit
+`
+
+ tx := txntest.Txn{
+ Type: protocol.ApplicationCallTx,
+ }.SignedTxn()
+ ledger := MakeLedger(nil)
+ ledger.NewApp(tx.Txn.Receiver, 888, basics.AppParams{})
+ ledger.NewAccount(appAddr(888), 1000000)
+ short := pay + ";int 1"
+ long := pay + pay + pay + pay + pay + ";int 1"
+ // First two just replicate the non-pooled test
+ one := MakeSampleTxnGroup(tx)
+ TestApps(t, []string{short, ""}, one, LogicVersion, ledger)
+ TestApps(t, []string{short, ""}, one, LogicVersion, ledger)
+ TestApps(t, []string{short, ""}, one, LogicVersion, ledger)
+ TestApps(t, []string{short, ""}, one, LogicVersion, ledger)
+ TestApps(t, []string{short, ""}, one, LogicVersion, ledger)
+ TestApps(t, []string{short, ""}, one, LogicVersion, ledger)
+ TestApps(t, []string{long, ""}, one, LogicVersion, ledger,
+ NewExpect(0, "too many inner transactions"))
+
+ // Now try pooling. But it won't work, because in `one`, only the first txn
+ // is an appcall.
+ TestApps(t, []string{long, short}, one, LogicVersion, ledger,
+ NewExpect(0, "too many inner transactions"))
+ TestApps(t, []string{short, long}, one, LogicVersion, ledger,
+ NewExpect(1, "too many inner transactions"))
+
+ // Now show pooling works, whether the first txn is heavy, or the second (but not both)
+ two := MakeSampleTxnGroup(tx)
+ two[1].Txn.Type = protocol.ApplicationCallTx
+ TestApps(t, []string{short, long}, two, LogicVersion, ledger)
+ TestApps(t, []string{long, short}, two, LogicVersion, ledger)
+ TestApps(t, []string{long, long}, two, LogicVersion, ledger,
+ NewExpect(1, "too many inner transactions"))
+
}
func TestAssetCreate(t *testing.T) {
@@ -443,12 +501,12 @@ func TestAssetCreate(t *testing.T) {
itxn_submit
int 1
`
- ep, tx, ledger := makeSampleEnv()
+ ep, tx, ledger := MakeSampleEnv()
ledger.NewApp(tx.Receiver, 888, basics.AppParams{})
- testApp(t, create, ep, "insufficient balance")
+ TestApp(t, create, ep, "insufficient balance")
// Give it enough for fee. Recall that we don't check min balance at this level.
- ledger.NewAccount(ledger.ApplicationID().Address(), defaultEvalProto().MinTxnFee)
- testApp(t, create, ep)
+ ledger.NewAccount(appAddr(888), MakeTestProto().MinTxnFee)
+ TestApp(t, create, ep)
}
func TestAssetFreeze(t *testing.T) {
@@ -463,98 +521,98 @@ func TestAssetFreeze(t *testing.T) {
global CurrentApplicationAddress ; itxn_field ConfigAssetFreeze;
itxn_submit
itxn CreatedAssetID
- int 889
+ int 5000
==
`
- ep, tx, ledger := makeSampleEnv()
+ ep, tx, ledger := MakeSampleEnv()
ledger.NewApp(tx.Receiver, 888, basics.AppParams{})
// Give it enough for fees. Recall that we don't check min balance at this level.
- ledger.NewAccount(ledger.ApplicationID().Address(), 12*defaultEvalProto().MinTxnFee)
- testApp(t, create, ep)
+ ledger.NewAccount(appAddr(888), 12*MakeTestProto().MinTxnFee)
+ TestApp(t, create, ep)
freeze := `
itxn_begin
int afrz ; itxn_field TypeEnum
- int 889 ; itxn_field FreezeAsset
+ int 5000 ; itxn_field FreezeAsset
txn ApplicationArgs 0; btoi ; itxn_field FreezeAssetFrozen
txn Accounts 1 ; itxn_field FreezeAssetAccount
itxn_submit
int 1
`
- testApp(t, freeze, ep, "invalid Asset reference")
- tx.ForeignAssets = []basics.AssetIndex{basics.AssetIndex(889)}
+ TestApp(t, freeze, ep, "invalid Asset reference")
+ tx.ForeignAssets = []basics.AssetIndex{basics.AssetIndex(5000)}
tx.ApplicationArgs = [][]byte{{0x01}}
- testApp(t, freeze, ep, "does not hold Asset")
- ledger.NewHolding(tx.Receiver, 889, 55, false)
- testApp(t, freeze, ep)
- holding, err := ledger.AssetHolding(tx.Receiver, 889)
+ TestApp(t, freeze, ep, "does not hold Asset")
+ ledger.NewHolding(tx.Receiver, 5000, 55, false)
+ TestApp(t, freeze, ep)
+ holding, err := ledger.AssetHolding(tx.Receiver, 5000)
require.NoError(t, err)
require.Equal(t, true, holding.Frozen)
tx.ApplicationArgs = [][]byte{{0x00}}
- testApp(t, freeze, ep)
- holding, err = ledger.AssetHolding(tx.Receiver, 889)
+ TestApp(t, freeze, ep)
+ holding, err = ledger.AssetHolding(tx.Receiver, 5000)
require.NoError(t, err)
require.Equal(t, false, holding.Frozen)
}
func TestFieldSetting(t *testing.T) {
- ep, tx, ledger := makeSampleEnv()
+ ep, tx, ledger := MakeSampleEnv()
ledger.NewApp(tx.Receiver, 888, basics.AppParams{})
- ledger.NewAccount(ledger.ApplicationID().Address(), 10*defaultEvalProto().MinTxnFee)
- testApp(t, "itxn_begin; int 500; bzero; itxn_field Note; int 1", ep)
- testApp(t, "itxn_begin; int 501; bzero; itxn_field Note; int 1", ep,
+ ledger.NewAccount(appAddr(888), 10*MakeTestProto().MinTxnFee)
+ TestApp(t, "itxn_begin; int 500; bzero; itxn_field Note; int 1", ep)
+ TestApp(t, "itxn_begin; int 501; bzero; itxn_field Note; int 1", ep,
"Note may not exceed")
- testApp(t, "itxn_begin; int 32; bzero; itxn_field VotePK; int 1", ep)
- testApp(t, "itxn_begin; int 31; bzero; itxn_field VotePK; int 1", ep,
+ TestApp(t, "itxn_begin; int 32; bzero; itxn_field VotePK; int 1", ep)
+ TestApp(t, "itxn_begin; int 31; bzero; itxn_field VotePK; int 1", ep,
"VotePK must be 32")
- testApp(t, "itxn_begin; int 32; bzero; itxn_field SelectionPK; int 1", ep)
- testApp(t, "itxn_begin; int 33; bzero; itxn_field SelectionPK; int 1", ep,
+ TestApp(t, "itxn_begin; int 32; bzero; itxn_field SelectionPK; int 1", ep)
+ TestApp(t, "itxn_begin; int 33; bzero; itxn_field SelectionPK; int 1", ep,
"SelectionPK must be 32")
- testApp(t, "itxn_begin; int 32; bzero; itxn_field RekeyTo; int 1", ep)
- testApp(t, "itxn_begin; int 31; bzero; itxn_field RekeyTo; int 1", ep,
+ TestApp(t, "itxn_begin; int 32; bzero; itxn_field RekeyTo; int 1", ep)
+ TestApp(t, "itxn_begin; int 31; bzero; itxn_field RekeyTo; int 1", ep,
"not an address")
- testApp(t, "itxn_begin; int 6; bzero; itxn_field ConfigAssetUnitName; int 1", ep)
- testApp(t, "itxn_begin; int 7; bzero; itxn_field ConfigAssetUnitName; int 1", ep,
+ TestApp(t, "itxn_begin; int 6; bzero; itxn_field ConfigAssetUnitName; int 1", ep)
+ TestApp(t, "itxn_begin; int 7; bzero; itxn_field ConfigAssetUnitName; int 1", ep,
"value is too long")
- testApp(t, "itxn_begin; int 12; bzero; itxn_field ConfigAssetName; int 1", ep)
- testApp(t, "itxn_begin; int 13; bzero; itxn_field ConfigAssetName; int 1", ep,
+ TestApp(t, "itxn_begin; int 12; bzero; itxn_field ConfigAssetName; int 1", ep)
+ TestApp(t, "itxn_begin; int 13; bzero; itxn_field ConfigAssetName; int 1", ep,
"value is too long")
}
func TestInnerGroup(t *testing.T) {
- ep, tx, ledger := makeSampleEnv()
+ ep, tx, ledger := MakeSampleEnv()
ledger.NewApp(tx.Receiver, 888, basics.AppParams{})
// Need both fees and both payments
- ledger.NewAccount(ledger.ApplicationID().Address(), 999+2*defaultEvalProto().MinTxnFee)
+ ledger.NewAccount(appAddr(888), 999+2*MakeTestProto().MinTxnFee)
pay := `
int pay; itxn_field TypeEnum;
int 500; itxn_field Amount;
txn Sender; itxn_field Receiver;
`
- testApp(t, "itxn_begin"+pay+"itxn_next"+pay+"itxn_submit; int 1", ep,
+ TestApp(t, "itxn_begin"+pay+"itxn_next"+pay+"itxn_submit; int 1", ep,
"insufficient balance")
// NewAccount overwrites the existing balance
- ledger.NewAccount(ledger.ApplicationID().Address(), 1000+2*defaultEvalProto().MinTxnFee)
- testApp(t, "itxn_begin"+pay+"itxn_next"+pay+"itxn_submit; int 1", ep)
+ ledger.NewAccount(appAddr(888), 1000+2*MakeTestProto().MinTxnFee)
+ TestApp(t, "itxn_begin"+pay+"itxn_next"+pay+"itxn_submit; int 1", ep)
}
func TestInnerFeePooling(t *testing.T) {
- ep, tx, ledger := makeSampleEnv()
+ ep, tx, ledger := MakeSampleEnv()
ledger.NewApp(tx.Receiver, 888, basics.AppParams{})
- ledger.NewAccount(ledger.ApplicationID().Address(), 50_000)
+ ledger.NewAccount(appAddr(888), 50_000)
pay := `
int pay; itxn_field TypeEnum;
int 500; itxn_field Amount;
txn Sender; itxn_field Receiver;
`
// Force the first fee to 3, but the second will default to 2*fee-3 = 2002-3
- testApp(t, "itxn_begin"+
+ TestApp(t, "itxn_begin"+
pay+
"int 3; itxn_field Fee;"+
"itxn_next"+
@@ -562,7 +620,7 @@ txn Sender; itxn_field Receiver;
"itxn_submit; itxn Fee; int 1999; ==", ep)
// Same first, but force the second too low
- testApp(t, "itxn_begin"+
+ TestApp(t, "itxn_begin"+
pay+
"int 3; itxn_field Fee;"+
"itxn_next"+
@@ -571,7 +629,7 @@ txn Sender; itxn_field Receiver;
"itxn_submit; int 1", ep, "fee too small")
// Overpay in first itxn, the second will default to less
- testApp(t, "itxn_begin"+
+ TestApp(t, "itxn_begin"+
pay+
"int 2000; itxn_field Fee;"+
"itxn_next"+
@@ -579,7 +637,7 @@ txn Sender; itxn_field Receiver;
"itxn_submit; itxn Fee; int 2; ==", ep)
// Same first, but force the second too low
- testApp(t, "itxn_begin"+
+ TestApp(t, "itxn_begin"+
pay+
"int 2000; itxn_field Fee;"+
"itxn_next"+
@@ -592,83 +650,83 @@ txn Sender; itxn_field Receiver;
// constructed not what can be submitted, so it tests what "bad" fields cause
// immediate failures.
func TestApplCreation(t *testing.T) {
- ep, tx, _ := makeSampleEnv()
+ ep, tx, _ := MakeSampleEnv()
p := "itxn_begin;"
s := "; int 1"
- testApp(t, p+"int 31; itxn_field ApplicationID"+s, ep,
+ TestApp(t, p+"int 31; itxn_field ApplicationID"+s, ep,
"invalid App reference")
tx.ForeignApps = append(tx.ForeignApps, 31)
- testApp(t, p+"int 31; itxn_field ApplicationID"+s, ep)
-
- testApp(t, p+"int 0; itxn_field OnCompletion"+s, ep)
- testApp(t, p+"int 1; itxn_field OnCompletion"+s, ep)
- testApp(t, p+"int 2; itxn_field OnCompletion"+s, ep)
- testApp(t, p+"int 3; itxn_field OnCompletion"+s, ep)
- testApp(t, p+"int 4; itxn_field OnCompletion"+s, ep)
- testApp(t, p+"int 5; itxn_field OnCompletion"+s, ep)
- testApp(t, p+"int 6; itxn_field OnCompletion"+s, ep, "6 is larger than max=5")
- testApp(t, p+"int NoOp; itxn_field OnCompletion"+s, ep)
- testApp(t, p+"int OptIn; itxn_field OnCompletion"+s, ep)
- testApp(t, p+"int CloseOut; itxn_field OnCompletion"+s, ep)
- testApp(t, p+"int ClearState; itxn_field OnCompletion"+s, ep)
- testApp(t, p+"int UpdateApplication; itxn_field OnCompletion"+s, ep)
- testApp(t, p+"int DeleteApplication; itxn_field OnCompletion"+s, ep)
-
- testApp(t, p+"int 800; bzero; itxn_field ApplicationArgs"+s, ep)
- testApp(t, p+"int 801; bzero; itxn_field ApplicationArgs", ep,
+ TestApp(t, p+"int 31; itxn_field ApplicationID"+s, ep)
+
+ TestApp(t, p+"int 0; itxn_field OnCompletion"+s, ep)
+ TestApp(t, p+"int 1; itxn_field OnCompletion"+s, ep)
+ TestApp(t, p+"int 2; itxn_field OnCompletion"+s, ep)
+ TestApp(t, p+"int 3; itxn_field OnCompletion"+s, ep)
+ TestApp(t, p+"int 4; itxn_field OnCompletion"+s, ep)
+ TestApp(t, p+"int 5; itxn_field OnCompletion"+s, ep)
+ TestApp(t, p+"int 6; itxn_field OnCompletion"+s, ep, "6 is larger than max=5")
+ TestApp(t, p+"int NoOp; itxn_field OnCompletion"+s, ep)
+ TestApp(t, p+"int OptIn; itxn_field OnCompletion"+s, ep)
+ TestApp(t, p+"int CloseOut; itxn_field OnCompletion"+s, ep)
+ TestApp(t, p+"int ClearState; itxn_field OnCompletion"+s, ep)
+ TestApp(t, p+"int UpdateApplication; itxn_field OnCompletion"+s, ep)
+ TestApp(t, p+"int DeleteApplication; itxn_field OnCompletion"+s, ep)
+
+ TestApp(t, p+"int 800; bzero; itxn_field ApplicationArgs"+s, ep)
+ TestApp(t, p+"int 801; bzero; itxn_field ApplicationArgs", ep,
"length too long")
- testApp(t, p+"int 401; bzero; dup; itxn_field ApplicationArgs; itxn_field ApplicationArgs", ep,
+ TestApp(t, p+"int 401; bzero; dup; itxn_field ApplicationArgs; itxn_field ApplicationArgs", ep,
"length too long")
- testApp(t, p+strings.Repeat("byte 0x11; itxn_field ApplicationArgs;", 12)+s, ep)
- testApp(t, p+strings.Repeat("byte 0x11; itxn_field ApplicationArgs;", 13)+s, ep,
+ TestApp(t, p+strings.Repeat("byte 0x11; itxn_field ApplicationArgs;", 12)+s, ep)
+ TestApp(t, p+strings.Repeat("byte 0x11; itxn_field ApplicationArgs;", 13)+s, ep,
"too many application args")
- testApp(t, p+strings.Repeat("int 32; bzero; itxn_field Accounts;", 3)+s, ep,
+ TestApp(t, p+strings.Repeat("int 32; bzero; itxn_field Accounts;", 3)+s, ep,
"invalid Account reference")
tx.Accounts = append(tx.Accounts, basics.Address{})
- testApp(t, fmt.Sprintf(p+"%s"+s,
+ TestApp(t, fmt.Sprintf(p+"%s"+s,
strings.Repeat("int 32; bzero; itxn_field Accounts;", 3)), ep)
- testApp(t, fmt.Sprintf(p+"%s"+s,
+ TestApp(t, fmt.Sprintf(p+"%s"+s,
strings.Repeat("int 32; bzero; itxn_field Accounts;", 4)), ep,
"too many foreign accounts")
- testApp(t, p+strings.Repeat("int 621; itxn_field Applications;", 5)+s, ep,
+ TestApp(t, p+strings.Repeat("int 621; itxn_field Applications;", 5)+s, ep,
"invalid App reference")
tx.ForeignApps = append(tx.ForeignApps, basics.AppIndex(621))
- testApp(t, p+strings.Repeat("int 621; itxn_field Applications;", 5)+s, ep)
- testApp(t, p+strings.Repeat("int 621; itxn_field Applications;", 6)+s, ep,
+ TestApp(t, p+strings.Repeat("int 621; itxn_field Applications;", 5)+s, ep)
+ TestApp(t, p+strings.Repeat("int 621; itxn_field Applications;", 6)+s, ep,
"too many foreign apps")
- testApp(t, p+strings.Repeat("int 621; itxn_field Assets;", 6)+s, ep,
+ TestApp(t, p+strings.Repeat("int 621; itxn_field Assets;", 6)+s, ep,
"invalid Asset reference")
tx.ForeignAssets = append(tx.ForeignAssets, basics.AssetIndex(621))
- testApp(t, p+strings.Repeat("int 621; itxn_field Assets;", 6)+s, ep)
- testApp(t, p+strings.Repeat("int 621; itxn_field Assets;", 7)+s, ep,
+ TestApp(t, p+strings.Repeat("int 621; itxn_field Assets;", 6)+s, ep)
+ TestApp(t, p+strings.Repeat("int 621; itxn_field Assets;", 7)+s, ep,
"too many foreign assets")
- testApp(t, p+"int 2700; bzero; itxn_field ApprovalProgram"+s, ep)
- testApp(t, p+"int 2701; bzero; itxn_field ApprovalProgram"+s, ep,
+ TestApp(t, p+"int 2700; bzero; itxn_field ApprovalProgram"+s, ep)
+ TestApp(t, p+"int 2701; bzero; itxn_field ApprovalProgram"+s, ep,
"may not exceed 2700")
- testApp(t, p+"int 2700; bzero; itxn_field ClearStateProgram"+s, ep)
- testApp(t, p+"int 2701; bzero; itxn_field ClearStateProgram"+s, ep,
+ TestApp(t, p+"int 2700; bzero; itxn_field ClearStateProgram"+s, ep)
+ TestApp(t, p+"int 2701; bzero; itxn_field ClearStateProgram"+s, ep,
"may not exceed 2700")
- testApp(t, p+"int 30; itxn_field GlobalNumUint"+s, ep)
- testApp(t, p+"int 31; itxn_field GlobalNumUint"+s, ep, "31 is larger than max=30")
- testApp(t, p+"int 30; itxn_field GlobalNumByteSlice"+s, ep)
- testApp(t, p+"int 31; itxn_field GlobalNumByteSlice"+s, ep, "31 is larger than max=30")
- testApp(t, p+"int 20; itxn_field GlobalNumUint; int 11; itxn_field GlobalNumByteSlice"+s, ep)
+ TestApp(t, p+"int 30; itxn_field GlobalNumUint"+s, ep)
+ TestApp(t, p+"int 31; itxn_field GlobalNumUint"+s, ep, "31 is larger than max=30")
+ TestApp(t, p+"int 30; itxn_field GlobalNumByteSlice"+s, ep)
+ TestApp(t, p+"int 31; itxn_field GlobalNumByteSlice"+s, ep, "31 is larger than max=30")
+ TestApp(t, p+"int 20; itxn_field GlobalNumUint; int 11; itxn_field GlobalNumByteSlice"+s, ep)
- testApp(t, p+"int 13; itxn_field LocalNumUint"+s, ep)
- testApp(t, p+"int 14; itxn_field LocalNumUint"+s, ep, "14 is larger than max=13")
- testApp(t, p+"int 13; itxn_field LocalNumByteSlice"+s, ep)
- testApp(t, p+"int 14; itxn_field LocalNumByteSlice"+s, ep, "14 is larger than max=13")
+ TestApp(t, p+"int 13; itxn_field LocalNumUint"+s, ep)
+ TestApp(t, p+"int 14; itxn_field LocalNumUint"+s, ep, "14 is larger than max=13")
+ TestApp(t, p+"int 13; itxn_field LocalNumByteSlice"+s, ep)
+ TestApp(t, p+"int 14; itxn_field LocalNumByteSlice"+s, ep, "14 is larger than max=13")
- testApp(t, p+"int 2; itxn_field ExtraProgramPages"+s, ep)
- testApp(t, p+"int 3; itxn_field ExtraProgramPages"+s, ep, "3 is larger than max=2")
+ TestApp(t, p+"int 2; itxn_field ExtraProgramPages"+s, ep)
+ TestApp(t, p+"int 3; itxn_field ExtraProgramPages"+s, ep, "3 is larger than max=2")
}
// TestApplSubmission tests for checking of illegal appl transaction in form
@@ -676,56 +734,56 @@ func TestApplCreation(t *testing.T) {
// error. These are not exhaustive, but certainly demonstrate that WellFormed
// is getting a crack at the txn.
func TestApplSubmission(t *testing.T) {
- ep, tx, ledger := makeSampleEnv()
+ ep, tx, ledger := MakeSampleEnv()
ledger.NewApp(tx.Receiver, 888, basics.AppParams{})
// Since the fee is moved first, fund the app
- ledger.NewAccount(ledger.ApplicationID().Address(), 50_000)
+ ledger.NewAccount(appAddr(888), 50_000)
- ops := testProg(t, "int 1", AssemblerMaxVersion)
+ ops := TestProg(t, "int 1", AssemblerMaxVersion)
approve := hex.EncodeToString(ops.Program)
a := fmt.Sprintf("byte 0x%s; itxn_field ApprovalProgram;", approve)
p := "itxn_begin; int appl; itxn_field TypeEnum;"
s := ";itxn_submit; int 1"
- testApp(t, p+a+s, ep)
+ TestApp(t, p+a+s, ep)
// All zeros is v0, so we get a complaint, but that means lengths were ok.
- testApp(t, p+a+`int 600; bzero; itxn_field ApprovalProgram;
+ TestApp(t, p+a+`int 600; bzero; itxn_field ApprovalProgram;
int 600; bzero; itxn_field ClearStateProgram;`+s, ep,
"program version must be")
- testApp(t, p+`int 601; bzero; itxn_field ApprovalProgram;
+ TestApp(t, p+`int 601; bzero; itxn_field ApprovalProgram;
int 600; bzero; itxn_field ClearStateProgram;`+s, ep, "too long")
// WellFormed does the math based on the supplied ExtraProgramPages
- testApp(t, p+a+`int 1; itxn_field ExtraProgramPages
+ TestApp(t, p+a+`int 1; itxn_field ExtraProgramPages
int 1200; bzero; itxn_field ApprovalProgram;
int 1200; bzero; itxn_field ClearStateProgram;`+s, ep,
"program version must be")
- testApp(t, p+`int 1; itxn_field ExtraProgramPages
+ TestApp(t, p+`int 1; itxn_field ExtraProgramPages
int 1200; bzero; itxn_field ApprovalProgram;
int 1201; bzero; itxn_field ClearStateProgram;`+s, ep, "too long")
// Can't set epp when app id is given
tx.ForeignApps = append(tx.ForeignApps, basics.AppIndex(7))
- testApp(t, p+`int 1; itxn_field ExtraProgramPages;
+ TestApp(t, p+`int 1; itxn_field ExtraProgramPages;
int 7; itxn_field ApplicationID`+s, ep, "immutable")
- testApp(t, p+"int 20; itxn_field GlobalNumUint; int 11; itxn_field GlobalNumByteSlice"+s,
+ TestApp(t, p+"int 20; itxn_field GlobalNumUint; int 11; itxn_field GlobalNumByteSlice"+s,
ep, "too large")
- testApp(t, p+"int 7; itxn_field LocalNumUint; int 7; itxn_field LocalNumByteSlice"+s,
+ TestApp(t, p+"int 7; itxn_field LocalNumUint; int 7; itxn_field LocalNumByteSlice"+s,
ep, "too large")
}
func TestInnerApplCreate(t *testing.T) {
- ep, tx, ledger := makeSampleEnv()
+ ep, tx, ledger := MakeSampleEnv()
ledger.NewApp(tx.Receiver, 888, basics.AppParams{})
- ledger.NewAccount(ledger.ApplicationID().Address(), 50_000)
+ ledger.NewAccount(appAddr(888), 50_000)
- ops := testProg(t, "int 1", AssemblerMaxVersion)
+ ops := TestProg(t, "int 1", AssemblerMaxVersion)
approve := "byte 0x" + hex.EncodeToString(ops.Program)
- testApp(t, `
+ TestApp(t, `
itxn_begin
int appl; itxn_field TypeEnum
`+approve+`; itxn_field ApprovalProgram
@@ -736,36 +794,34 @@ int 3; itxn_field LocalNumUint
itxn_submit
int 1
`, ep)
- // In testing, creating an app sets the "current app". So reset it.
- ledger.SetApp(888)
- testApp(t, `
-int 889; app_params_get AppGlobalNumByteSlice; assert; int 0; ==; assert
+ TestApp(t, `
+int 5000; app_params_get AppGlobalNumByteSlice; assert; int 0; ==; assert
`, ep, "invalid App reference")
call := `
itxn_begin
int appl; itxn_field TypeEnum
-int 889; itxn_field ApplicationID
+int 5000; itxn_field ApplicationID
itxn_submit
int 1
`
// Can't call it either
- testApp(t, call, ep, "invalid App reference")
-
- tx.ForeignApps = []basics.AppIndex{basics.AppIndex(889)}
- testApp(t, `
-int 889; app_params_get AppGlobalNumByteSlice; assert; int 0; ==; assert
-int 889; app_params_get AppGlobalNumUint; assert; int 1; ==; assert
-int 889; app_params_get AppLocalNumByteSlice; assert; int 2; ==; assert
-int 889; app_params_get AppLocalNumUint; assert; int 3; ==; assert
+ TestApp(t, call, ep, "invalid App reference")
+
+ tx.ForeignApps = []basics.AppIndex{basics.AppIndex(5000)}
+ TestApp(t, `
+int 5000; app_params_get AppGlobalNumByteSlice; assert; int 0; ==; assert
+int 5000; app_params_get AppGlobalNumUint; assert; int 1; ==; assert
+int 5000; app_params_get AppLocalNumByteSlice; assert; int 2; ==; assert
+int 5000; app_params_get AppLocalNumUint; assert; int 3; ==; assert
int 1
`, ep)
// Call it (default OnComplete is NoOp)
- testApp(t, call, ep)
+ TestApp(t, call, ep)
- testApp(t, `
+ TestApp(t, `
itxn_begin
int appl; itxn_field TypeEnum
int DeleteApplication; itxn_field OnCompletion
@@ -775,24 +831,24 @@ int 1
`, ep)
// App is gone
- testApp(t, `
-int 889; app_params_get AppGlobalNumByteSlice; !; assert; !; assert; int 1
+ TestApp(t, `
+int 5000; app_params_get AppGlobalNumByteSlice; !; assert; !; assert; int 1
`, ep)
// Can't call it either
- testApp(t, call, ep, "No application")
+ TestApp(t, call, ep, "No application")
}
func TestCreateOldAppFails(t *testing.T) {
- ep, tx, ledger := makeSampleEnv()
+ ep, tx, ledger := MakeSampleEnv()
ledger.NewApp(tx.Receiver, 888, basics.AppParams{})
- ledger.NewAccount(ledger.ApplicationID().Address(), 50_000)
+ ledger.NewAccount(appAddr(888), 50_000)
- ops := testProg(t, "int 1", innerAppsEnabledVersion-1)
+ ops := TestProg(t, "int 1", InnerAppsEnabledVersion-1)
approve := "byte 0x" + hex.EncodeToString(ops.Program)
- testApp(t, `
+ TestApp(t, `
itxn_begin
int appl; itxn_field TypeEnum
`+approve+`; itxn_field ApprovalProgram
@@ -806,22 +862,22 @@ int 1
}
func TestSelfReentrancy(t *testing.T) {
- ep, tx, ledger := makeSampleEnv()
+ ep, tx, ledger := MakeSampleEnv()
ledger.NewApp(tx.Receiver, 888, basics.AppParams{})
- ledger.NewAccount(ledger.ApplicationID().Address(), 50_000)
+ ledger.NewAccount(appAddr(888), 50_000)
- testApp(t, `
+ TestApp(t, `
itxn_begin
int appl; itxn_field TypeEnum
int 888; itxn_field ApplicationID
itxn_submit
int 1
-`, ep, "attempt to re-enter 888")
+`, ep, "attempt to self-call")
}
func TestIndirectReentrancy(t *testing.T) {
- ep, tx, ledger := makeSampleEnv()
- call888 := testProg(t, `itxn_begin
+ ep, tx, ledger := MakeSampleEnv()
+ call888 := TestProg(t, `itxn_begin
int appl; itxn_field TypeEnum
int 888; itxn_field ApplicationID
itxn_submit
@@ -832,24 +888,49 @@ int 1
})
ledger.NewApp(tx.Receiver, 888, basics.AppParams{})
- ledger.NewAccount(ledger.ApplicationID().Address(), 50_000)
+ ledger.NewAccount(appAddr(888), 50_000)
tx.ForeignApps = []basics.AppIndex{basics.AppIndex(222)}
- testApp(t, `
+ TestApp(t, `
itxn_begin
int appl; itxn_field TypeEnum
int 222; itxn_field ApplicationID
+int 888; itxn_field Applications
itxn_submit
int 1
`, ep, "attempt to re-enter 888")
}
+// TestInnerAppID ensures that inner app properly sees its AppId. This seems
+// needlessly picky to test, but the appID used to be stored outside the cx.
+func TestInnerAppID(t *testing.T) {
+ ep, tx, ledger := MakeSampleEnv()
+ logID := TestProg(t, `global CurrentApplicationID; itob; log; int 1`, AssemblerMaxVersion)
+ ledger.NewApp(tx.Receiver, 222, basics.AppParams{
+ ApprovalProgram: logID.Program,
+ })
+
+ ledger.NewApp(tx.Receiver, 888, basics.AppParams{})
+ ledger.NewAccount(appAddr(888), 50_000)
+ tx.ForeignApps = []basics.AppIndex{basics.AppIndex(222)}
+ TestApp(t, `
+itxn_begin
+int appl; itxn_field TypeEnum
+int 222; itxn_field ApplicationID
+itxn_submit
+itxn Logs 0
+btoi
+int 222
+==
+`, ep)
+}
+
// TestInnerBudgetIncrement ensures that an app can make a (nearly) empty inner
// app call in order to get 700 extra opcode budget. Unfortunately, it costs a
// bit to create the call, and the app itself consumes 1, so it ends up being
// about 690 (see next test).
func TestInnerBudgetIncrement(t *testing.T) {
- ep, tx, ledger := makeSampleEnv()
- gasup := testProg(t, "pushint 1", AssemblerMaxVersion)
+ ep, tx, ledger := MakeSampleEnv()
+ gasup := TestProg(t, "pushint 1", AssemblerMaxVersion)
ledger.NewApp(tx.Receiver, 222, basics.AppParams{
ApprovalProgram: gasup.Program,
})
@@ -862,21 +943,21 @@ itxn_submit;
`
ledger.NewApp(tx.Receiver, 888, basics.AppParams{})
- ledger.NewAccount(ledger.ApplicationID().Address(), 50_000)
+ ledger.NewAccount(appAddr(888), 50_000)
tx.ForeignApps = []basics.AppIndex{basics.AppIndex(222)}
- testApp(t, strings.Repeat(waste, 5)+"int 1", ep)
- testApp(t, strings.Repeat(waste, 6)+"int 1", ep, "dynamic cost budget exceeded")
- testApp(t, strings.Repeat(waste, 6)+buy+"int 1", ep, "dynamic cost budget exceeded")
- testApp(t, buy+strings.Repeat(waste, 6)+"int 1", ep)
- testApp(t, buy+strings.Repeat(waste, 10)+"int 1", ep)
- testApp(t, buy+strings.Repeat(waste, 12)+"int 1", ep, "dynamic cost budget exceeded")
- testApp(t, buy+strings.Repeat(waste, 12)+"int 1", ep, "dynamic cost budget exceeded")
- testApp(t, buy+buy+strings.Repeat(waste, 12)+"int 1", ep)
+ TestApp(t, strings.Repeat(waste, 5)+"int 1", ep)
+ TestApp(t, strings.Repeat(waste, 6)+"int 1", ep, "dynamic cost budget exceeded")
+ TestApp(t, strings.Repeat(waste, 6)+buy+"int 1", ep, "dynamic cost budget exceeded")
+ TestApp(t, buy+strings.Repeat(waste, 6)+"int 1", ep)
+ TestApp(t, buy+strings.Repeat(waste, 10)+"int 1", ep)
+ TestApp(t, buy+strings.Repeat(waste, 12)+"int 1", ep, "dynamic cost budget exceeded")
+ TestApp(t, buy+strings.Repeat(waste, 12)+"int 1", ep, "dynamic cost budget exceeded")
+ TestApp(t, buy+buy+strings.Repeat(waste, 12)+"int 1", ep)
}
func TestIncrementCheck(t *testing.T) {
- ep, tx, ledger := makeSampleEnv()
- gasup := testProg(t, "pushint 1", AssemblerMaxVersion)
+ ep, tx, ledger := MakeSampleEnv()
+ gasup := TestProg(t, "pushint 1", AssemblerMaxVersion)
ledger.NewApp(tx.Receiver, 222, basics.AppParams{
ApprovalProgram: gasup.Program,
})
@@ -899,22 +980,22 @@ int 1
`
ledger.NewApp(tx.Receiver, 888, basics.AppParams{})
- ledger.NewAccount(ledger.ApplicationID().Address(), 50_000)
+ ledger.NewAccount(appAddr(888), 50_000)
tx.ForeignApps = []basics.AppIndex{basics.AppIndex(222)}
- testApp(t, source, ep)
+ TestApp(t, source, ep)
}
// TestInnerTxIDs confirms that TxIDs are available and different
func TestInnerTxIDs(t *testing.T) {
- ep, tx, ledger := makeSampleEnv()
- txid := testProg(t, "txn TxID; log; int 1", AssemblerMaxVersion)
+ ep, tx, ledger := MakeSampleEnv()
+ txid := TestProg(t, "txn TxID; log; int 1", AssemblerMaxVersion)
ledger.NewApp(tx.Receiver, 222, basics.AppParams{
ApprovalProgram: txid.Program,
})
ledger.NewApp(tx.Receiver, 888, basics.AppParams{})
- ledger.NewAccount(ledger.ApplicationID().Address(), 50_000)
+ ledger.NewAccount(appAddr(888), 50_000)
tx.ForeignApps = []basics.AppIndex{basics.AppIndex(222)}
- testApp(t, `
+ TestApp(t, `
itxn_begin
int appl; itxn_field TypeEnum
int 222; itxn_field ApplicationID
@@ -934,17 +1015,17 @@ itxn Logs 0
// TestInnerGroupIDs confirms that GroupIDs are unset on size one inner groups,
// but set and unique on non-singletons
func TestInnerGroupIDs(t *testing.T) {
- ep, tx, ledger := makeSampleEnv()
- gid := testProg(t, "global GroupID; log; int 1", AssemblerMaxVersion)
+ ep, tx, ledger := MakeSampleEnv()
+ gid := TestProg(t, "global GroupID; log; int 1", AssemblerMaxVersion)
ledger.NewApp(tx.Receiver, 222, basics.AppParams{
ApprovalProgram: gid.Program,
})
ledger.NewApp(tx.Receiver, 888, basics.AppParams{})
- ledger.NewAccount(ledger.ApplicationID().Address(), 50_000)
+ ledger.NewAccount(appAddr(888), 50_000)
tx.ForeignApps = []basics.AppIndex{basics.AppIndex(222)}
// A single txn gets 0 group id
- testApp(t, `
+ TestApp(t, `
itxn_begin
int appl; itxn_field TypeEnum
int 222; itxn_field ApplicationID
@@ -955,7 +1036,7 @@ global ZeroAddress
`, ep)
// A double calls gets something else
- testApp(t, `
+ TestApp(t, `
itxn_begin
int appl; itxn_field TypeEnum
int 222; itxn_field ApplicationID
@@ -969,7 +1050,7 @@ global ZeroAddress
`, ep)
// The "something else" is unique, despite two identical groups
- testApp(t, `
+ TestApp(t, `
itxn_begin
int appl; itxn_field TypeEnum
int 222; itxn_field ApplicationID
@@ -994,25 +1075,25 @@ itxn Logs 0
// TestGtixn confirms access to itxn groups
func TestGtixn(t *testing.T) {
- ep, tx, ledger := makeSampleEnv()
- two := testProg(t, "byte 0x22; log; int 1", AssemblerMaxVersion)
+ ep, tx, ledger := MakeSampleEnv()
+ two := TestProg(t, "byte 0x22; log; int 1", AssemblerMaxVersion)
ledger.NewApp(tx.Receiver, 222, basics.AppParams{
ApprovalProgram: two.Program,
})
- three := testProg(t, "byte 0x33; log; int 1", AssemblerMaxVersion)
+ three := TestProg(t, "byte 0x33; log; int 1", AssemblerMaxVersion)
ledger.NewApp(tx.Receiver, 333, basics.AppParams{
ApprovalProgram: three.Program,
})
- four := testProg(t, "byte 0x44; log; int 1", AssemblerMaxVersion)
+ four := TestProg(t, "byte 0x44; log; int 1", AssemblerMaxVersion)
ledger.NewApp(tx.Receiver, 444, basics.AppParams{
ApprovalProgram: four.Program,
})
ledger.NewApp(tx.Receiver, 888, basics.AppParams{})
- ledger.NewAccount(ledger.ApplicationID().Address(), 50_000)
+ ledger.NewAccount(appAddr(888), 50_000)
tx.ForeignApps = []basics.AppIndex{basics.AppIndex(222), basics.AppIndex(333), basics.AppIndex(444)}
- testApp(t, `
+ TestApp(t, `
itxn_begin
int appl; itxn_field TypeEnum
int 222; itxn_field ApplicationID
@@ -1052,7 +1133,7 @@ int 1
`, ep)
// Confirm that two singletons don't get treated as a group
- testApp(t, `
+ TestApp(t, `
itxn_begin
int appl; itxn_field TypeEnum
int 222; itxn_field ApplicationID
@@ -1072,21 +1153,21 @@ int 1
// TestGtxnLog confirms that gtxn can now access previous txn's Logs.
func TestGtxnLog(t *testing.T) {
- ep, tx, ledger := makeSampleEnv()
- two := testProg(t, "byte 0x22; log; int 1", AssemblerMaxVersion)
+ ep, tx, ledger := MakeSampleEnv()
+ two := TestProg(t, "byte 0x22; log; int 1", AssemblerMaxVersion)
ledger.NewApp(tx.Receiver, 222, basics.AppParams{
ApprovalProgram: two.Program,
})
- three := testProg(t, "gtxn 0 NumLogs; int 1; ==; assert; gtxna 0 Logs 0; byte 0x22; ==", AssemblerMaxVersion)
+ three := TestProg(t, "gtxn 0 NumLogs; int 1; ==; assert; gtxna 0 Logs 0; byte 0x22; ==", AssemblerMaxVersion)
ledger.NewApp(tx.Receiver, 333, basics.AppParams{
ApprovalProgram: three.Program,
})
ledger.NewApp(tx.Receiver, 888, basics.AppParams{})
- ledger.NewAccount(ledger.ApplicationID().Address(), 50_000)
+ ledger.NewAccount(appAddr(888), 50_000)
tx.ForeignApps = []basics.AppIndex{basics.AppIndex(222), basics.AppIndex(333)}
- testApp(t, `itxn_begin
+ TestApp(t, `itxn_begin
int appl; itxn_field TypeEnum
int 222; itxn_field ApplicationID
itxn_next
@@ -1099,8 +1180,8 @@ int 1
// TestGtxnApps confirms that gtxn can now access previous txn's created app id.
func TestGtxnApps(t *testing.T) {
- ep, tx, ledger := makeSampleEnv()
- appcheck := testProg(t, `
+ ep, tx, ledger := MakeSampleEnv()
+ appcheck := TestProg(t, `
gtxn 0 CreatedApplicationID; itob; log;
gtxn 1 CreatedApplicationID; itob; log;
int 1
@@ -1109,12 +1190,12 @@ int 1
ApprovalProgram: appcheck.Program,
})
- ops := testProg(t, "int 1", AssemblerMaxVersion)
+ ops := TestProg(t, "int 1", AssemblerMaxVersion)
ledger.NewApp(tx.Receiver, 888, basics.AppParams{})
- ledger.NewAccount(ledger.ApplicationID().Address(), 50_000)
+ ledger.NewAccount(appAddr(888), 50_000)
tx.ForeignApps = []basics.AppIndex{basics.AppIndex(222)}
- testApp(t, `itxn_begin
+ TestApp(t, `itxn_begin
int appl; itxn_field TypeEnum
`+fmt.Sprintf("byte 0x%s; itxn_field ApprovalProgram;", hex.EncodeToString(ops.Program))+`
itxn_next
@@ -1126,20 +1207,20 @@ int 222; itxn_field ApplicationID
itxn_submit
itxn Logs 0
btoi
-int 889
+int 5000
==
assert
gitxn 2 Logs 1
btoi
-int 890
+int 5001
==
`, ep)
}
// TestGtxnAsa confirms that gtxn can now access previous txn's created asa id.
func TestGtxnAsa(t *testing.T) {
- ep, tx, ledger := makeSampleEnv()
- appcheck := testProg(t, `
+ ep, tx, ledger := MakeSampleEnv()
+ appcheck := TestProg(t, `
gtxn 0 CreatedAssetID; itob; log;
gtxn 1 CreatedAssetID; itob; log;
int 1
@@ -1149,9 +1230,9 @@ int 1
})
ledger.NewApp(tx.Receiver, 888, basics.AppParams{})
- ledger.NewAccount(ledger.ApplicationID().Address(), 50_000)
+ ledger.NewAccount(appAddr(888), 50_000)
tx.ForeignApps = []basics.AppIndex{basics.AppIndex(222)}
- testApp(t, `itxn_begin
+ TestApp(t, `itxn_begin
int acfg; itxn_field TypeEnum
itxn_next
int acfg; itxn_field TypeEnum
@@ -1161,20 +1242,20 @@ int 222; itxn_field ApplicationID
itxn_submit
itxn Logs 0
btoi
-int 889
+int 5000
==
assert
gitxn 2 Logs 1
btoi
-int 890
+int 5001
==
`, ep)
}
// TestCallerGlobals checks that a called app can see its caller.
func TestCallerGlobals(t *testing.T) {
- ep, tx, ledger := makeSampleEnv()
- globals := testProg(t, fmt.Sprintf(`
+ ep, tx, ledger := MakeSampleEnv()
+ globals := TestProg(t, fmt.Sprintf(`
global CallerApplicationID
int 888
==
@@ -1188,12 +1269,156 @@ addr %s
})
ledger.NewApp(tx.Receiver, 888, basics.AppParams{})
- ledger.NewAccount(ledger.ApplicationID().Address(), 50_000)
+ ledger.NewAccount(appAddr(888), 50_000)
tx.ForeignApps = []basics.AppIndex{basics.AppIndex(222)}
- testApp(t, `itxn_begin
+ TestApp(t, `itxn_begin
int appl; itxn_field TypeEnum
int 222; itxn_field ApplicationID
itxn_submit
int 1
`, ep)
}
+
+// TestNumInnerDeep ensures that inner call limits apply to inner calls of inner
+// transactions.
+func TestNumInnerDeep(t *testing.T) {
+ pay := `
+ itxn_begin
+ int 1
+ itxn_field Amount
+ txn Accounts 0
+ itxn_field Receiver
+ int pay
+ itxn_field TypeEnum
+ itxn_submit
+`
+
+ tx := txntest.Txn{
+ Type: protocol.ApplicationCallTx,
+ ApplicationID: 888,
+ ForeignApps: []basics.AppIndex{basics.AppIndex(222)},
+ }.SignedTxnWithAD()
+ require.Equal(t, 888, int(tx.Txn.ApplicationID))
+ ledger := MakeLedger(nil)
+
+ pay3 := TestProg(t, pay+pay+pay+"int 1;", AssemblerMaxVersion).Program
+ ledger.NewApp(tx.Txn.Receiver, 222, basics.AppParams{
+ ApprovalProgram: pay3,
+ })
+
+ ledger.NewApp(tx.Txn.Receiver, 888, basics.AppParams{})
+ ledger.NewAccount(appAddr(888), 1_000_000)
+
+ callpay3 := `itxn_begin
+int appl; itxn_field TypeEnum
+int 222; itxn_field ApplicationID
+itxn_submit
+`
+ txg := []transactions.SignedTxnWithAD{tx}
+ ep := NewAppEvalParams(txg, MakeTestProto(), &transactions.SpecialAddresses{}, 0)
+ ep.Ledger = ledger
+ TestApp(t, callpay3+"int 1", ep, "insufficient balance") // inner contract needs money
+
+ ledger.NewAccount(appAddr(222), 1_000_000)
+ TestApp(t, callpay3+"int 1", ep)
+ TestApp(t, callpay3+callpay3+"int 1", ep, "too many inner transactions")
+}
+
+// TestCreateAndUse checks that an ASA can be created in an inner app, and then
+// used. This was not allowed until v6, because of the strict adherence to the
+// foreign-arrays rules.
+func TestCreateAndUse(t *testing.T) {
+ axfer := `
+ itxn_begin
+ int acfg; itxn_field TypeEnum
+ int 10; itxn_field ConfigAssetTotal
+ byte "Gold"; itxn_field ConfigAssetName
+ itxn_submit
+
+ itxn_begin
+ int axfer; itxn_field TypeEnum
+ itxn CreatedAssetID; itxn_field XferAsset
+ txn Accounts 0; itxn_field AssetReceiver
+ itxn_submit
+
+ int 1
+`
+
+ // First testing in axfer
+ ep, tx, ledger := MakeSampleEnv()
+ ledger.NewApp(tx.Receiver, 888, basics.AppParams{})
+ ledger.NewAccount(appAddr(888), 4*MakeTestProto().MinTxnFee)
+ TestApp(t, axfer, ep)
+
+ ep.Proto = MakeTestProtoV(CreatedResourcesVersion - 1)
+ TestApp(t, axfer, ep, "invalid Asset reference")
+
+ balance := `
+ itxn_begin
+ int acfg; itxn_field TypeEnum
+ int 10; itxn_field ConfigAssetTotal
+ byte "Gold"; itxn_field ConfigAssetName
+ itxn_submit
+
+ // txn Sender is not opted-in, as it's the app account that made the asset
+ // At some point, we should short-circuit so this does not go to disk.
+ txn Sender
+ itxn CreatedAssetID
+ asset_holding_get AssetBalance
+ int 0
+ ==
+ assert
+ int 0
+ ==
+ assert
+
+ // App account owns all the newly made gold
+ global CurrentApplicationAddress
+ itxn CreatedAssetID
+ asset_holding_get AssetBalance
+ assert
+ int 10
+ ==
+ assert
+
+ int 1
+`
+
+ // Now as in asset balance opcode
+ ep, tx, ledger = MakeSampleEnv()
+ ledger.NewApp(tx.Receiver, 888, basics.AppParams{})
+ ledger.NewAccount(appAddr(888), 4*MakeTestProto().MinTxnFee)
+ TestApp(t, balance, ep)
+
+ ep.Proto = MakeTestProtoV(CreatedResourcesVersion - 1)
+ TestApp(t, balance, ep, "invalid Asset reference")
+
+ appcall := `
+ itxn_begin
+ int acfg; itxn_field TypeEnum
+ int 10; itxn_field ConfigAssetTotal
+ byte "Gold"; itxn_field ConfigAssetName
+ itxn_submit
+
+ itxn_begin
+ int appl; itxn_field TypeEnum
+ int 888; itxn_field ApplicationID
+ itxn CreatedAssetID; itxn_field Assets
+ itxn_submit
+
+ int 1
+`
+
+ // Now as ForeigAsset
+ ep, tx, ledger = MakeSampleEnv()
+ ledger.NewApp(tx.Receiver, 888, basics.AppParams{})
+ ledger.NewApp(tx.Receiver, 888, basics.AppParams{})
+ ledger.NewAccount(appAddr(888), 4*MakeTestProto().MinTxnFee)
+ // It gets passed the Assets setting
+ TestApp(t, appcall, ep, "attempt to self-call")
+
+ // Appcall is isn't allowed pre-CreatedResourcesVersion, because same
+ // version allowed inner app calls
+ // ep.Proto = MakeTestProtoV(CreatedResourcesVersion - 1)
+ // TestApp(t, appcall, ep, "invalid Asset reference")
+}
diff --git a/data/transactions/logic/evalCrypto_test.go b/data/transactions/logic/evalCrypto_test.go
index 2fae16c516..fad04b926b 100644
--- a/data/transactions/logic/evalCrypto_test.go
+++ b/data/transactions/logic/evalCrypto_test.go
@@ -196,9 +196,9 @@ byte 0x%s
&&`
pkTampered1 := make([]byte, len(pk))
copy(pkTampered1, pk)
- pkTampered1[0] = 0
- pkTampered2 := make([]byte, len(pk))
- copy(pkTampered2, pk[1:])
+ pkTampered1[0] = 0 // first byte is a prefix of either 0x02 or 0x03
+ pkTampered2 := make([]byte, len(pk)-1) // must be 33 bytes length
+ copy(pkTampered2, pk)
var decompressTests = []struct {
key []byte
@@ -208,8 +208,9 @@ byte 0x%s
{pkTampered1, false},
{pkTampered2, false},
}
- for _, test := range decompressTests {
+ for i, test := range decompressTests {
t.Run(fmt.Sprintf("decompress/pass=%v", test.pass), func(t *testing.T) {
+ t.Log("decompressTests i", i)
src := fmt.Sprintf(source, hex.EncodeToString(test.key), hex.EncodeToString(x), hex.EncodeToString(y))
if test.pass {
testAccepts(t, src, 5)
@@ -239,8 +240,8 @@ ecdsa_verify Secp256k1
v := int(sign[64])
rTampered := make([]byte, len(r))
- copy(rTampered, pk)
- rTampered[0] = 0
+ copy(rTampered, r)
+ rTampered[0] += byte(1) // intentional overflow
var verifyTests = []struct {
data string
diff --git a/data/transactions/logic/evalStateful_test.go b/data/transactions/logic/evalStateful_test.go
index 855c4f05ea..5266e123cb 100644
--- a/data/transactions/logic/evalStateful_test.go
+++ b/data/transactions/logic/evalStateful_test.go
@@ -313,7 +313,7 @@ func TestBalance(t *testing.T) {
text = `txn Accounts 1; balance; int 177; ==;`
// won't assemble in old version teal
- testProg(t, text, directRefEnabledVersion-1, expect{2, "balance arg 0 wanted type uint64..."})
+ testProg(t, text, directRefEnabledVersion-1, Expect{2, "balance arg 0 wanted type uint64..."})
// but legal after that
testApp(t, text, ep)
@@ -327,26 +327,30 @@ func TestBalance(t *testing.T) {
testApp(t, text, ep)
}
-func testApps(t *testing.T, programs []string, ep *EvalParams, expected ...expect) {
+func testApps(t *testing.T, programs []string, txgroup []transactions.SignedTxn, version uint64, ledger LedgerForLogic,
+ expected ...Expect) {
t.Helper()
- codes := make([][]byte, len(ep.TxnGroup))
- for i, source := range programs {
- if source != "" {
- codes[i] = testProg(t, source, ep.Proto.LogicSigVersion).Program
+ codes := make([][]byte, len(programs))
+ for i, program := range programs {
+ if program != "" {
+ codes[i] = testProg(t, program, version).Program
}
}
+ ep := NewAppEvalParams(transactions.WrapSignedTxnsWithAD(txgroup), makeTestProtoV(version), &transactions.SpecialAddresses{}, 0)
+ ep.Ledger = ledger
testAppsBytes(t, codes, ep, expected...)
}
-func testAppsBytes(t *testing.T, programs [][]byte, ep *EvalParams, expected ...expect) {
+func testAppsBytes(t *testing.T, programs [][]byte, ep *EvalParams, expected ...Expect) {
t.Helper()
- ep.reset() // Only reset at beginning, so testApp calls see changes.
+ require.Equal(t, len(programs), len(ep.TxnGroup))
for i := range ep.TxnGroup {
- if programs != nil {
+ if programs[i] != nil {
if len(expected) > 0 && expected[0].l == i {
- testAppFull(t, programs[i], i, ep, expected[0].s)
+ testAppFull(t, programs[i], i, basics.AppIndex(888), ep, expected[0].s)
+ break // Stop after first failure
} else {
- testAppFull(t, programs[i], i, ep)
+ testAppFull(t, programs[i], i, basics.AppIndex(888), ep)
}
}
}
@@ -361,14 +365,18 @@ func testApp(t *testing.T, program string, ep *EvalParams, problems ...string) t
func testAppBytes(t *testing.T, program []byte, ep *EvalParams, problems ...string) transactions.EvalDelta {
t.Helper()
ep.reset()
- return testAppFull(t, program, 0, ep, problems...)
+ aid := ep.TxnGroup[0].Txn.ApplicationID
+ if aid == basics.AppIndex(0) {
+ aid = basics.AppIndex(888)
+ }
+ return testAppFull(t, program, 0, aid, ep, problems...)
}
// testAppFull gives a lot of control to caller - in particular, notice that
// ep.reset() is in testAppBytes, not here. This means that ADs in the ep are
// not cleared, so repeated use of a single ep is probably not a good idea
// unless you are *intending* to see how ep is modified as you go.
-func testAppFull(t *testing.T, program []byte, gi int, ep *EvalParams, problems ...string) transactions.EvalDelta {
+func testAppFull(t *testing.T, program []byte, gi int, aid basics.AppIndex, ep *EvalParams, problems ...string) transactions.EvalDelta {
t.Helper()
var checkProblem string
@@ -387,7 +395,7 @@ func testAppFull(t *testing.T, program []byte, gi int, ep *EvalParams, problems
sb := &strings.Builder{}
ep.Trace = sb
- err := CheckContract(program, gi, ep)
+ err := CheckContract(program, ep)
if checkProblem == "" {
require.NoError(t, err, sb.String())
} else {
@@ -400,22 +408,11 @@ func testAppFull(t *testing.T, program []byte, gi int, ep *EvalParams, problems
// may mean that the problems argument is often duplicated, but this seems
// the best way to be concise about all sorts of tests.
- /* Hackish: Reset budget, since ep often gets reused in tests. But don't
- reset it if it's currently set to a positive multiple of the
- MaxAppProgramCost. That allows a test that's trying to simulate a group
- of app calls being pooled. */
- if ep.PooledApplicationBudget != nil {
- current := *ep.PooledApplicationBudget
- if current == 0 || (current%uint64(ep.Proto.MaxAppProgramCost) != 0) {
- *ep.PooledApplicationBudget = uint64(ep.Proto.MaxAppProgramCost)
- }
- }
-
if ep.Ledger == nil {
ep.Ledger = MakeLedger(nil)
}
- pass, err := EvalApp(program, gi, ep)
+ pass, err := EvalApp(program, gi, aid, ep)
delta := ep.TxnGroup[gi].EvalDelta
if evalProblem == "" {
require.NoError(t, err, "Eval%s\nExpected: PASS", sb)
@@ -464,7 +461,7 @@ func TestMinBalance(t *testing.T) {
testApp(t, "int 1; min_balance; int 1001; ==", ep) // 1 == Accounts[0]
testProg(t, "txn Accounts 1; min_balance; int 1001; ==", directRefEnabledVersion-1,
- expect{2, "min_balance arg 0 wanted type uint64..."})
+ Expect{2, "min_balance arg 0 wanted type uint64..."})
testProg(t, "txn Accounts 1; min_balance; int 1001; ==", directRefEnabledVersion)
testApp(t, "txn Accounts 1; min_balance; int 1001; ==", ep) // 1 == Accounts[0]
// Receiver opts in
@@ -512,7 +509,7 @@ func TestAppCheckOptedIn(t *testing.T) {
testApp(t, "int 1; int 2; app_opted_in; int 0; ==", pre) // in pre, int 2 is an actual app id
testApp(t, "byte \"aoeuiaoeuiaoeuiaoeuiaoeuiaoeui01\"; int 2; app_opted_in; int 1; ==", now)
testProg(t, "byte \"aoeuiaoeuiaoeuiaoeuiaoeuiaoeui01\"; int 2; app_opted_in; int 1; ==", directRefEnabledVersion-1,
- expect{3, "app_opted_in arg 0 wanted type uint64..."})
+ Expect{3, "app_opted_in arg 0 wanted type uint64..."})
// Sender opted in
ledger.NewLocals(txn.Txn.Sender, 100)
@@ -580,12 +577,12 @@ byte 0x414c474f
testApp(t, text, now)
testApp(t, strings.Replace(text, "int 1 // account idx", "byte \"aoeuiaoeuiaoeuiaoeuiaoeuiaoeui01\"", -1), now)
testProg(t, strings.Replace(text, "int 1 // account idx", "byte \"aoeuiaoeuiaoeuiaoeuiaoeuiaoeui01\"", -1), directRefEnabledVersion-1,
- expect{4, "app_local_get_ex arg 0 wanted type uint64..."})
+ Expect{4, "app_local_get_ex arg 0 wanted type uint64..."})
testApp(t, strings.Replace(text, "int 100 // app id", "int 2", -1), now)
// Next we're testing if the use of the current app's id works
// as a direct reference. The error is because the sender
// account is not opted into 123.
- ledger.NewApp(now.TxnGroup[0].Txn.RekeyTo, 123, basics.AppParams{})
+ now.TxnGroup[0].Txn.ApplicationID = 123
testApp(t, strings.Replace(text, "int 100 // app id", "int 123", -1), now, "no app for account")
testApp(t, strings.Replace(text, "int 100 // app id", "int 2", -1), pre, "no app for account")
testApp(t, strings.Replace(text, "int 100 // app id", "int 9", -1), now, "invalid App reference 9")
@@ -636,10 +633,11 @@ byte 0x414c474f
==`
ledger.NewLocal(now.TxnGroup[0].Txn.Sender, 100, string(protocol.PaymentTx), basics.TealValue{Type: basics.TealBytesType, Bytes: "ALGO"})
+ now.TxnGroup[0].Txn.ApplicationID = 100
testApp(t, text, now)
testApp(t, strings.Replace(text, "int 0 // account idx", "byte \"aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00\"", -1), now)
testProg(t, strings.Replace(text, "int 0 // account idx", "byte \"aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00\"", -1), directRefEnabledVersion-1,
- expect{3, "app_local_get arg 0 wanted type uint64..."})
+ Expect{3, "app_local_get arg 0 wanted type uint64..."})
testApp(t, strings.Replace(text, "int 0 // account idx", "byte \"aoeuiaoeuiaoeuiaoeuiaoeuiaoeui01\"", -1), now)
testApp(t, strings.Replace(text, "int 0 // account idx", "byte \"aoeuiaoeuiaoeuiaoeuiaoeuiaoeui02\"", -1), now,
"invalid Account reference")
@@ -732,13 +730,15 @@ int 4141
// specified in the transaction
now.TxnGroup[0].Txn.ApplicationID = 0
now.TxnGroup[0].Txn.ForeignApps = []basics.AppIndex{100}
- testApp(t, text, now)
+
+ testAppFull(t, testProg(t, text, LogicVersion).Program, 0, 100, now)
// Direct reference to the current app also works
- ledger.NewApp(now.TxnGroup[0].Txn.Receiver, 100, basics.AppParams{})
now.TxnGroup[0].Txn.ForeignApps = []basics.AppIndex{}
- testApp(t, strings.Replace(text, "int 1 // ForeignApps index", "int 100", -1), now)
- testApp(t, strings.Replace(text, "int 1 // ForeignApps index", "global CurrentApplicationID", -1), now)
+ testAppFull(t, testProg(t, strings.Replace(text, "int 1 // ForeignApps index", "int 100", -1), LogicVersion).Program,
+ 0, 100, now)
+ testAppFull(t, testProg(t, strings.Replace(text, "int 1 // ForeignApps index", "global CurrentApplicationID", -1), LogicVersion).Program,
+ 0, 100, now)
}
const assetsTestTemplate = `int 0//account
@@ -900,7 +900,7 @@ func testAssetsByVersion(t *testing.T, assetsTestProgram string, version uint64)
// it wasn't legal to use a direct ref for account
testProg(t, `byte "aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00"; int 54; asset_holding_get AssetBalance`,
- directRefEnabledVersion-1, expect{3, "asset_holding_get AssetBalance arg 0 wanted type uint64..."})
+ directRefEnabledVersion-1, Expect{3, "asset_holding_get AssetBalance arg 0 wanted type uint64..."})
// but it is now (empty asset yields 0,0 on stack)
testApp(t, `byte "aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00"; int 55; asset_holding_get AssetBalance; ==`, now)
// This is receiver, who is in Assets array
@@ -943,7 +943,7 @@ func testAssetsByVersion(t *testing.T, assetsTestProgram string, version uint64)
testApp(t, strings.Replace(assetsTestProgram, "int 55", "int 0", -1), now)
// but old code cannot
- testProg(t, strings.Replace(assetsTestProgram, "int 0//account", "byte \"aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00\"", -1), directRefEnabledVersion-1, expect{3, "asset_holding_get AssetBalance arg 0 wanted type uint64..."})
+ testProg(t, strings.Replace(assetsTestProgram, "int 0//account", "byte \"aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00\"", -1), directRefEnabledVersion-1, Expect{3, "asset_holding_get AssetBalance arg 0 wanted type uint64..."})
if version < 5 {
// Can't run these with AppCreator anyway
@@ -973,7 +973,7 @@ intc_2 // 1
ops := testProg(t, source, version)
require.Equal(t, OpsByName[now.Proto.LogicSigVersion]["asset_holding_get"].Opcode, ops.Program[8])
ops.Program[9] = 0x02
- _, err := EvalApp(ops.Program, 0, now)
+ _, err := EvalApp(ops.Program, 0, 0, now)
require.Error(t, err)
require.Contains(t, err.Error(), "invalid asset_holding_get field 2")
@@ -998,7 +998,7 @@ intc_1
ops = testProg(t, source, version)
require.Equal(t, OpsByName[now.Proto.LogicSigVersion]["asset_params_get"].Opcode, ops.Program[6])
ops.Program[7] = 0x20
- _, err = EvalApp(ops.Program, 0, now)
+ _, err = EvalApp(ops.Program, 0, 0, now)
require.Error(t, err)
require.Contains(t, err.Error(), "invalid asset_params_get field 32")
@@ -1137,7 +1137,7 @@ intc_1
txn := makeSampleTxn()
txn.Txn.ApplicationID = 100
ep := defaultEvalParams(&txn)
- err := CheckContract(ops.Program, 0, ep)
+ err := CheckContract(ops.Program, ep)
require.NoError(t, err)
ledger := MakeLedger(
@@ -1150,12 +1150,12 @@ intc_1
saved := ops.Program[firstCmdOffset]
require.Equal(t, OpsByName[0]["intc_0"].Opcode, saved)
ops.Program[firstCmdOffset] = OpsByName[0]["intc_1"].Opcode
- _, err = EvalApp(ops.Program, 0, ep)
+ _, err = EvalApp(ops.Program, 0, 100, ep)
require.Error(t, err)
require.Contains(t, err.Error(), "invalid Account reference 100")
ops.Program[firstCmdOffset] = saved
- _, err = EvalApp(ops.Program, 0, ep)
+ _, err = EvalApp(ops.Program, 0, 100, ep)
require.Error(t, err)
require.Contains(t, err.Error(), "no app for account")
@@ -1163,7 +1163,7 @@ intc_1
ledger.NewLocals(txn.Txn.Sender, 100)
if name == "read" {
- _, err = EvalApp(ops.Program, 0, ep)
+ _, err = EvalApp(ops.Program, 0, 100, ep)
require.Error(t, err)
require.Contains(t, err.Error(), "err opcode") // no such key
}
@@ -1172,7 +1172,7 @@ intc_1
ledger.NewLocal(txn.Txn.Sender, 100, "ALGOA", basics.TealValue{Type: basics.TealUintType, Uint: 1})
ledger.Reset()
- pass, err := EvalApp(ops.Program, 0, ep)
+ pass, err := EvalApp(ops.Program, 0, 100, ep)
require.NoError(t, err)
require.True(t, pass)
delta := ep.TxnGroup[0].EvalDelta
@@ -1455,31 +1455,15 @@ int 1
ops, err := AssembleStringWithVersion(source, AssemblerMaxVersion)
require.NoError(t, err)
- txn := makeSampleTxn()
- ep := defaultEvalParams(&txn)
- _, err = EvalApp(ops.Program, 0, ep)
- require.Error(t, err)
- require.Contains(t, err.Error(), "no ledger in contract eval")
+ ep, txn, ledger := makeSampleEnv()
+ txn.ApplicationID = basics.AppIndex(100)
+ testAppBytes(t, ops.Program, ep, "no such app")
- ledger := MakeLedger(
- map[basics.Address]uint64{
- txn.Txn.Sender: 1,
- },
- )
- ep.Ledger = ledger
-
- txn.Txn.ApplicationID = 100
- _, err = EvalApp(ops.Program, 0, ep)
- require.Error(t, err)
- require.Contains(t, err.Error(), "no such app")
-
- ledger.NewApp(txn.Txn.Sender, 100, makeApp(0, 0, 1, 0))
+ ledger.NewApp(txn.Sender, 100, makeApp(0, 0, 1, 0))
// a special test for read
if name == "read" {
- _, err = EvalApp(ops.Program, 0, ep)
- require.Error(t, err)
- require.Contains(t, err.Error(), "err opcode") // no such key
+ testAppBytes(t, ops.Program, ep, "err opcode") // no such key
}
ledger.NewGlobal(100, "ALGO", basics.TealValue{Type: basics.TealUintType, Uint: 0x77})
@@ -1978,7 +1962,7 @@ int 1
delta = testApp(t, strings.Replace(source, "int 0 // sender", "byte \"aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00\"", -1), ep)
// But won't even compile in old teal
testProg(t, strings.Replace(source, "int 0 // sender", "byte \"aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00\"", -1), directRefEnabledVersion-1,
- expect{4, "app_local_put arg 0 wanted..."}, expect{11, "app_local_del arg 0 wanted..."})
+ Expect{4, "app_local_put arg 0 wanted..."}, Expect{11, "app_local_del arg 0 wanted..."})
require.Equal(t, 0, len(delta.GlobalDelta))
require.Equal(t, 2, len(delta.LocalDeltas))
@@ -2159,7 +2143,7 @@ func TestEnumFieldErrors(t *testing.T) {
source = `int 0
int 55
asset_holding_get AssetBalance
-pop
+assert
`
origBalanceFs := assetHoldingFieldSpecByField[AssetBalance]
badBalanceFs := origBalanceFs
@@ -2173,7 +2157,7 @@ pop
source = `int 0
asset_params_get AssetTotal
-pop
+assert
`
origTotalFs := assetParamsFieldSpecByField[AssetTotal]
badTotalFs := origTotalFs
@@ -2229,9 +2213,7 @@ func TestReturnTypes(t *testing.T) {
require.NoError(t, err)
algoValue := basics.TealValue{Type: basics.TealUintType, Uint: 0x77}
ledger.NewLocal(tx.Receiver, 1, string(key), algoValue)
- ledger.NewAccount(basics.AppIndex(1).Address(), 1000000)
-
- ep.Ledger = ledger
+ ledger.NewAccount(appAddr(1), 1000000)
specialCmd := map[string]string{
"txn": "txn Sender",
@@ -2263,7 +2245,7 @@ func TestReturnTypes(t *testing.T) {
"asset_params_get": "asset_params_get AssetTotal",
"asset_holding_get": "asset_holding_get AssetBalance",
"gtxns": "gtxns Sender",
- "gtxnsa": "gtxnsa ApplicationArgs 0",
+ "gtxnsa": "pop; int 0; gtxnsa ApplicationArgs 0",
"pushint": "pushint 7272",
"pushbytes": `pushbytes "jojogoodgorilla"`,
"app_params_get": "app_params_get AppGlobalNumUint",
@@ -2276,6 +2258,7 @@ func TestReturnTypes(t *testing.T) {
"itxna": "itxn_begin; int pay; itxn_field TypeEnum; itxn_submit; itxna Accounts 0",
"gitxn": "itxn_begin; int pay; itxn_field TypeEnum; itxn_submit; gitxn 0 Sender",
"gitxna": "itxn_begin; int pay; itxn_field TypeEnum; itxn_submit; gitxna 0 Accounts 0",
+ "base64_decode": `pushbytes "YWJjMTIzIT8kKiYoKSctPUB+"; base64_decode StdAlph; pushbytes "abc123!?$*&()'-=@~"; ==; pushbytes "YWJjMTIzIT8kKiYoKSctPUB-"; base64_decode URLAlph; pushbytes "abc123!?$*&()'-=@~"; ==; &&; assert`,
}
/* Make sure the specialCmd tests the opcode in question */
@@ -2314,6 +2297,7 @@ func TestReturnTypes(t *testing.T) {
var cx EvalContext
cx.EvalParams = ep
cx.runModeFlags = m
+ cx.appID = 1
// These two are silly, but one test needs a higher gi, and
// another needs to work on the args that were put in txn[0].
@@ -2361,8 +2345,8 @@ func TestLatestTimestamp(t *testing.T) {
func TestCurrentApplicationID(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()
- ep, tx, ledger := makeSampleEnv()
- ledger.NewApp(tx.Receiver, 42, basics.AppParams{})
+ ep, tx, _ := makeSampleEnv()
+ tx.ApplicationID = 42
source := "global CurrentApplicationID; int 42; =="
testApp(t, source, ep)
}
@@ -2395,23 +2379,29 @@ func TestPooledAppCallsVerifyOp(t *testing.T) {
pop
int 1`
- ep, _, _ := makeSampleEnv()
+ ledger := MakeLedger(nil)
+ call := transactions.SignedTxn{Txn: transactions.Transaction{Type: protocol.ApplicationCallTx}}
// Simulate test with 2 grouped txn
- *ep.PooledApplicationBudget = uint64(ep.Proto.MaxAppProgramCost * 2)
- testApp(t, source, ep, "pc=107 dynamic cost budget exceeded, executing ed25519verify: remaining budget is 1400 but program cost was 1905")
+ testApps(t, []string{source, ""}, []transactions.SignedTxn{call, call}, LogicVersion, ledger,
+ Expect{0, "pc=107 dynamic cost budget exceeded, executing ed25519verify: remaining budget is 1400 but program cost was 1905"})
// Simulate test with 3 grouped txn
- *ep.PooledApplicationBudget = uint64(ep.Proto.MaxAppProgramCost * 3)
- testApp(t, source, ep)
+ testApps(t, []string{source, "", ""}, []transactions.SignedTxn{call, call, call}, LogicVersion, ledger)
+}
+
+func appAddr(id int) basics.Address {
+ return basics.AppIndex(id).Address()
}
-func TestAppAddress(t *testing.T) {
+func TestAppInfo(t *testing.T) {
ep, tx, ledger := makeSampleEnv()
+ require.Equal(t, 888, int(tx.ApplicationID))
ledger.NewApp(tx.Receiver, 888, basics.AppParams{})
- source := fmt.Sprintf("global CurrentApplicationAddress; addr %s; ==;", basics.AppIndex(888).Address())
+ testApp(t, "global CurrentApplicationID; int 888; ==;", ep)
+ source := fmt.Sprintf("global CurrentApplicationAddress; addr %s; ==;", appAddr(888))
testApp(t, source, ep)
- source = fmt.Sprintf("int 0; app_params_get AppAddress; assert; addr %s; ==;", basics.AppIndex(888).Address())
+ source = fmt.Sprintf("int 0; app_params_get AppAddress; assert; addr %s; ==;", appAddr(888))
testApp(t, source, ep)
// To document easy construction:
diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go
index 46723c421e..be4a4c04c0 100644
--- a/data/transactions/logic/eval_test.go
+++ b/data/transactions/logic/eval_test.go
@@ -21,6 +21,7 @@ import (
"encoding/binary"
"encoding/hex"
"fmt"
+ "math"
"strconv"
"strings"
"testing"
@@ -38,14 +39,14 @@ import (
"github.com/algorand/go-algorand/test/partitiontest"
)
-// Note that most of the tests use defaultEvalProto/defaultEvalParams as evaluator version so that
+// Note that most of the tests use makeTestProto/defaultEvalParams as evaluator version so that
// we check that TEAL v1 and v2 programs are compatible with the latest evaluator
-func defaultEvalProto() config.ConsensusParams {
- return defaultEvalProtoWithVersion(LogicVersion)
+func makeTestProto() *config.ConsensusParams {
+ return makeTestProtoV(LogicVersion)
}
-func defaultEvalProtoWithVersion(version uint64) config.ConsensusParams {
- return config.ConsensusParams{
+func makeTestProtoV(version uint64) *config.ConsensusParams {
+ return &config.ConsensusParams{
LogicSigVersion: version,
LogicSigMaxCost: 20000,
Application: version >= appsEnabledVersion,
@@ -79,9 +80,10 @@ func defaultEvalProtoWithVersion(version uint64) config.ConsensusParams {
EnableFeePooling: true,
// Chosen to be different from one another and from normal proto
- MaxAppTxnAccounts: 3,
- MaxAppTxnForeignApps: 5,
- MaxAppTxnForeignAssets: 6,
+ MaxAppTxnAccounts: 3,
+ MaxAppTxnForeignApps: 5,
+ MaxAppTxnForeignAssets: 6,
+ MaxAppTotalTxnReferences: 7,
MaxAppArgs: 12,
MaxAppTotalArgLen: 800,
@@ -93,7 +95,8 @@ func defaultEvalProtoWithVersion(version uint64) config.ConsensusParams {
MaxGlobalSchemaEntries: 30,
MaxLocalSchemaEntries: 13,
- EnableAppCostPooling: true,
+ EnableAppCostPooling: true,
+ EnableInnerTransactionPooling: true,
}
}
@@ -114,22 +117,16 @@ func benchmarkEvalParams(txn *transactions.SignedTxn) *EvalParams {
}
func defaultEvalParamsWithVersion(txn *transactions.SignedTxn, version uint64) *EvalParams {
- proto := defaultEvalProtoWithVersion(version)
-
ep := &EvalParams{
- Proto: &proto,
- TxnGroup: make([]transactions.SignedTxnWithAD, 1),
- PastSideEffects: MakePastSideEffects(proto.MaxTxGroupSize),
- Specials: &transactions.SpecialAddresses{},
- Trace: &strings.Builder{},
+ Proto: makeTestProtoV(version),
+ TxnGroup: make([]transactions.SignedTxnWithAD, 1),
+ Specials: &transactions.SpecialAddresses{},
+ Trace: &strings.Builder{},
}
if txn != nil {
ep.TxnGroup[0].SignedTxn = *txn
}
- if proto.EnableAppCostPooling {
- budget := uint64(proto.MaxAppProgramCost)
- ep.PooledApplicationBudget = &budget
- }
+ ep.reset()
return ep
}
@@ -137,9 +134,27 @@ func defaultEvalParamsWithVersion(txn *transactions.SignedTxn, version uint64) *
// *_test.go because no real code should ever need this. EvalParams should be
// created to evaluate a group, and then thrown away.
func (ep *EvalParams) reset() {
+ if ep.Proto.EnableAppCostPooling {
+ budget := uint64(ep.Proto.MaxAppProgramCost)
+ ep.PooledApplicationBudget = &budget
+ }
+ if ep.Proto.EnableInnerTransactionPooling {
+ inners := ep.Proto.MaxInnerTransactions
+ ep.pooledAllowedInners = &inners
+ }
+ ep.PastSideEffects = MakePastSideEffects(ep.Proto.MaxTxGroupSize)
for i := range ep.TxnGroup {
ep.TxnGroup[i].ApplyData = transactions.ApplyData{}
}
+ if ep.Proto.LogicSigVersion < createdResourcesVersion {
+ ep.initialCounter = math.MaxUint64
+ } else {
+ if ep.Ledger != nil {
+ ep.initialCounter = ep.Ledger.Counter()
+ } else {
+ ep.initialCounter = firstTestID
+ }
+ }
}
func TestTooManyArgs(t *testing.T) {
@@ -866,14 +881,14 @@ func TestArg(t *testing.T) {
const globalV1TestProgram = `
global MinTxnFee
-int 123
+int 1001
==
global MinBalance
-int 1000000
+int 1001
==
&&
global MaxTxnLife
-int 999
+int 1500
==
&&
global ZeroAddress
@@ -902,7 +917,7 @@ int 0
>
&&
global CurrentApplicationID
-int 42
+int 888
==
&&
`
@@ -968,7 +983,7 @@ func TestGlobal(t *testing.T) {
ledger := MakeLedger(nil)
addr, err := basics.UnmarshalChecksumAddress(testAddr)
require.NoError(t, err)
- ledger.NewApp(addr, basics.AppIndex(42), basics.AppParams{})
+ ledger.NewApp(addr, 888, basics.AppParams{})
for v := uint64(1); v <= AssemblerMaxVersion; v++ {
_, ok := tests[v]
require.True(t, ok)
@@ -984,16 +999,7 @@ func TestGlobal(t *testing.T) {
txn := transactions.SignedTxn{}
txn.Txn.Group = crypto.Digest{0x07, 0x06}
- proto := config.ConsensusParams{
- MinTxnFee: 123,
- MinBalance: 1000000,
- MaxTxnLife: 999,
- LogicSigVersion: LogicVersion,
- LogicSigMaxCost: 20000,
- MaxAppProgramCost: 700,
- }
ep := defaultEvalParams(&txn)
- ep.Proto = &proto
ep.Ledger = ledger
testApp(t, tests[v].program, ep)
})
@@ -1180,7 +1186,7 @@ arg 8
`
const testTxnProgramTextV2 = testTxnProgramTextV1 + `txn ApplicationID
-int 123
+int 888
==
&&
txn OnCompletion
@@ -1403,7 +1409,7 @@ func makeSampleTxn() transactions.SignedTxn {
txn.Txn.AssetSender = txn.Txn.Receiver
txn.Txn.AssetReceiver = txn.Txn.CloseRemainderTo
txn.Txn.AssetCloseTo = txn.Txn.Sender
- txn.Txn.ApplicationID = basics.AppIndex(123)
+ txn.Txn.ApplicationID = basics.AppIndex(888)
txn.Txn.Accounts = make([]basics.Address, 1)
txn.Txn.Accounts[0] = txn.Txn.Receiver
rekeyToAddr := []byte("aoeuiaoeuiaoeuiaoeuiaoeuiaoeui05")
@@ -1447,17 +1453,25 @@ func makeSampleTxn() transactions.SignedTxn {
return txn
}
-func makeSampleTxnGroup(txn transactions.SignedTxn) []transactions.SignedTxn {
- txgroup := make([]transactions.SignedTxn, 2)
- txgroup[0] = txn
- txgroup[1].Txn.Amount.Raw = 42
- txgroup[1].Txn.Fee.Raw = 1066
- txgroup[1].Txn.FirstValid = 42
- txgroup[1].Txn.LastValid = 1066
- txgroup[1].Txn.Sender = txn.Txn.Receiver
- txgroup[1].Txn.Receiver = txn.Txn.Sender
- txgroup[1].Txn.ExtraProgramPages = 2
- return txgroup
+// makeSampleTxnGroup creates a sample txn group. If less than two transactions
+// are supplied, samples are used.
+func makeSampleTxnGroup(txns ...transactions.SignedTxn) []transactions.SignedTxn {
+ if len(txns) == 0 {
+ txns = []transactions.SignedTxn{makeSampleTxn()}
+ }
+ if len(txns) == 1 {
+ second := transactions.SignedTxn{}
+ second.Txn.Type = protocol.PaymentTx
+ second.Txn.Amount.Raw = 42
+ second.Txn.Fee.Raw = 1066
+ second.Txn.FirstValid = 42
+ second.Txn.LastValid = 1066
+ second.Txn.Sender = txns[0].Txn.Receiver
+ second.Txn.Receiver = txns[0].Txn.Sender
+ second.Txn.ExtraProgramPages = 2
+ txns = append(txns, second)
+ }
+ return txns
}
func TestTxn(t *testing.T) {
@@ -1543,7 +1557,7 @@ args
assert`, "", 1)
ops := testProg(t, appSafe, v)
- testAppFull(t, ops.Program, 3, ep)
+ testAppFull(t, ops.Program, 3, basics.AppIndex(888), ep)
}
})
}
@@ -1626,7 +1640,7 @@ int 100
ep := defaultEvalParams(nil)
ep.Ledger = ledger
ep.TxnGroup = transactions.WrapSignedTxnsWithAD(txgroup)
- pass, err := EvalApp(ops.Program, 1, ep)
+ pass, err := EvalApp(ops.Program, 1, 0, ep)
if !pass || err != nil {
t.Log(ep.Trace.String())
}
@@ -1641,26 +1655,26 @@ int 0
`
ops = testProg(t, futureCreatableIDProg, 4)
- _, err = EvalApp(ops.Program, 1, ep)
+ _, err = EvalApp(ops.Program, 1, 0, ep)
require.Error(t, err)
require.Contains(t, err.Error(), "gaid can't get creatable ID of txn ahead of the current one")
// should fail when accessing self
ops = testProg(t, checkCreatableIDProg, 4)
- _, err = EvalApp(ops.Program, 0, ep)
+ _, err = EvalApp(ops.Program, 0, 0, ep)
require.Error(t, err)
require.Contains(t, err.Error(), "gaid is only for accessing creatable IDs of previous txns")
// should fail on non-creatable
ep.TxnGroup[0].Txn.Type = protocol.PaymentTx
- _, err = EvalApp(ops.Program, 1, ep)
+ _, err = EvalApp(ops.Program, 1, 0, ep)
require.Error(t, err)
require.Contains(t, err.Error(), "can't use gaid on txn that is not an app call nor an asset config txn")
ep.TxnGroup[0].Txn.Type = protocol.AssetConfigTx
// should fail when no creatable was created
ledger.SetTrackedCreatable(0, basics.CreatableLocator{})
- _, err = EvalApp(ops.Program, 1, ep)
+ _, err = EvalApp(ops.Program, 1, 0, ep)
require.Error(t, err)
require.Contains(t, err.Error(), "the txn did not create anything")
}
@@ -2130,17 +2144,17 @@ func TestSubstringFlop(t *testing.T) {
// fails in compiler
testProg(t, `byte 0xf000000000000000
substring
-len`, 2, expect{2, "substring expects 2 immediate arguments"})
+len`, 2, Expect{2, "substring expects 2 immediate arguments"})
// fails in compiler
testProg(t, `byte 0xf000000000000000
substring 1
-len`, 2, expect{2, "substring expects 2 immediate arguments"})
+len`, 2, Expect{2, "substring expects 2 immediate arguments"})
// fails in compiler
testProg(t, `byte 0xf000000000000000
substring 4 2
-len`, 2, expect{2, "substring end is before start"})
+len`, 2, Expect{2, "substring end is before start"})
// fails at runtime
testPanics(t, `byte 0xf000000000000000
@@ -2193,11 +2207,11 @@ func TestExtractFlop(t *testing.T) {
// fails in compiler
testProg(t, `byte 0xf000000000000000
extract
- len`, 5, expect{2, "extract expects 2 immediate arguments"})
+ len`, 5, Expect{2, "extract expects 2 immediate arguments"})
testProg(t, `byte 0xf000000000000000
extract 1
- len`, 5, expect{2, "extract expects 2 immediate arguments"})
+ len`, 5, Expect{2, "extract expects 2 immediate arguments"})
// fails at runtime
err := testPanics(t, `byte 0xf000000000000000
@@ -2389,28 +2403,21 @@ int 1`,
cases := []scratchTestCase{
simpleCase, multipleTxnCase, selfCase, laterTxnSlotCase,
}
- proto := defaultEvalProtoWithVersion(LogicVersion)
for i, testCase := range cases {
t.Run(fmt.Sprintf("i=%d", i), func(t *testing.T) {
sources := testCase.tealSources
// Initialize txgroup
- txgroup := make([]transactions.SignedTxnWithAD, len(sources))
+ txgroup := make([]transactions.SignedTxn, len(sources))
for j := range txgroup {
- txgroup[j].SignedTxn.Txn.Type = protocol.ApplicationCallTx
- }
-
- ep := &EvalParams{
- Proto: &proto,
- TxnGroup: txgroup,
- PastSideEffects: MakePastSideEffects(len(sources)),
+ txgroup[j].Txn.Type = protocol.ApplicationCallTx
}
if testCase.errContains != "" {
- testApps(t, sources, ep, expect{testCase.errTxn, testCase.errContains})
+ testApps(t, sources, txgroup, LogicVersion, MakeLedger(nil), Expect{testCase.errTxn, testCase.errContains})
} else {
- testApps(t, sources, ep)
+ testApps(t, sources, txgroup, LogicVersion, MakeLedger(nil))
}
})
}
@@ -2453,7 +2460,7 @@ int 1`,
}
ep := &EvalParams{
- Proto: &proto,
+ Proto: makeTestProto(),
TxnGroup: txgroup,
PastSideEffects: MakePastSideEffects(2),
}
@@ -2516,14 +2523,7 @@ int 1
txgroup[j].Txn.Type = protocol.ApplicationCallTx
}
- proto := defaultEvalProtoWithVersion(LogicVersion)
- ep := &EvalParams{
- Proto: &proto,
- TxnGroup: transactions.WrapSignedTxnsWithAD(txgroup),
- PastSideEffects: MakePastSideEffects(len(sources)),
- }
-
- testApps(t, sources, ep)
+ testApps(t, sources, txgroup, LogicVersion, MakeLedger(nil))
}
const testCompareProgramText = `int 35
@@ -3408,6 +3408,54 @@ func BenchmarkBigMath(b *testing.B) {
}
}
+func BenchmarkBase64Decode(b *testing.B) {
+ smallStd := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
+ smallURL := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
+ medStd := strings.Repeat(smallStd, 16)
+ medURL := strings.Repeat(smallURL, 16)
+ bigStd := strings.Repeat(medStd, 4)
+ bigURL := strings.Repeat(medURL, 4)
+
+ tags := []string{"small", "medium", "large"}
+ stds := []string{smallStd, medStd, bigStd}
+ urls := []string{smallURL, medURL, bigURL}
+ ops := []string{
+ "",
+ "len",
+ "b~",
+ "int 1; pop",
+ "keccak256",
+ "sha256",
+ "sha512_256",
+ "base64_decode StdAlph",
+ "base64_decode URLAlph",
+ }
+ benches := [][]string{}
+ for i, tag := range tags {
+ for _, op := range ops {
+ testName := op
+ encoded := stds[i]
+ if op == "base64_decode URLAlph" {
+ encoded = urls[i]
+ }
+ if len(op) > 0 {
+ op += "; "
+ }
+ op += "pop"
+ benches = append(benches, []string{
+ fmt.Sprintf("%s_%s", testName, tag),
+ "",
+ fmt.Sprintf(`byte "%s"; %s`, encoded, op),
+ "int 1",
+ })
+ }
+ }
+ for _, bench := range benches {
+ b.Run(bench[0], func(b *testing.B) {
+ benchmarkOperation(b, bench[1], bench[2], bench[3])
+ })
+ }
+}
func BenchmarkAddx64(b *testing.B) {
progs := [][]string{
{"add long stack", addBenchmarkSource},
@@ -3726,9 +3774,9 @@ func TestAllowedOpcodesV2(t *testing.T) {
require.Contains(t, source, spec.Name)
ops := testProg(t, source, AssemblerMaxVersion)
// all opcodes allowed in stateful mode so use CheckStateful/EvalContract
- err := CheckContract(ops.Program, 0, ep)
+ err := CheckContract(ops.Program, ep)
require.NoError(t, err, source)
- _, err = EvalApp(ops.Program, 0, ep)
+ _, err = EvalApp(ops.Program, 0, 0, ep)
if spec.Name != "return" {
// "return" opcode always succeeds so ignore it
require.Error(t, err, source)
@@ -3828,7 +3876,7 @@ func testEvaluation(t *testing.T, program string, introduced uint64, tester eval
t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) {
t.Helper()
if v < introduced {
- testProg(t, obfuscate(program), v, expect{0, "...was introduced..."})
+ testProg(t, obfuscate(program), v, Expect{0, "...was introduced..."})
return
}
ops := testProg(t, program, v)
@@ -3837,21 +3885,19 @@ func testEvaluation(t *testing.T, program string, introduced uint64, tester eval
// EvalParams, so try all forward versions.
for lv := v; lv <= AssemblerMaxVersion; lv++ {
t.Run(fmt.Sprintf("lv=%d", lv), func(t *testing.T) {
- sb := strings.Builder{}
var txn transactions.SignedTxn
txn.Lsig.Logic = ops.Program
- err := CheckSignature(0, defaultEvalParamsWithVersion(&txn, lv))
+ ep := defaultEvalParamsWithVersion(&txn, lv)
+ err := CheckSignature(0, ep)
if err != nil {
- t.Log(hex.EncodeToString(ops.Program))
- t.Log(sb.String())
+ t.Log(ep.Trace.String())
}
require.NoError(t, err)
- sb = strings.Builder{}
- pass, err := EvalSignature(0, defaultEvalParamsWithVersion(&txn, lv))
+ ep = defaultEvalParamsWithVersion(&txn, lv)
+ pass, err := EvalSignature(0, ep)
ok := tester(pass, err)
if !ok {
- t.Log(hex.EncodeToString(ops.Program))
- t.Log(sb.String())
+ t.Log(ep.Trace.String())
t.Log(err)
}
require.True(t, ok)
@@ -4361,13 +4407,12 @@ func TestLog(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()
- proto := defaultEvalProtoWithVersion(LogicVersion)
var txn transactions.SignedTxn
txn.Txn.Type = protocol.ApplicationCallTx
ledger := MakeLedger(nil)
ledger.NewApp(txn.Txn.Receiver, 0, basics.AppParams{})
ep := defaultEvalParams(&txn)
- ep.Proto = &proto
+ ep.Proto = makeTestProtoV(LogicVersion)
ep.Ledger = ledger
testCases := []struct {
source string
@@ -4480,7 +4525,7 @@ func TestPcDetails(t *testing.T) {
ops := testProg(t, test.source, LogicVersion)
ep, _, _ := makeSampleEnv()
- pass, cx, err := EvalContract(ops.Program, 0, ep)
+ pass, cx, err := EvalContract(ops.Program, 0, 0, ep)
require.Error(t, err)
require.False(t, pass)
@@ -4492,3 +4537,67 @@ func TestPcDetails(t *testing.T) {
})
}
}
+
+var minB64DecodeVersion uint64 = 6
+
+func TestOpBase64Decode(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ testCases := []struct {
+ encoded string
+ alph string
+ decoded string
+ error string
+ }{
+ {"TU9CWS1ESUNLOwoKb3IsIFRIRSBXSEFMRS4KCgpCeSBIZXJtYW4gTWVsdmlsbGU=",
+ "StdAlph",
+ `MOBY-DICK;
+
+or, THE WHALE.
+
+
+By Herman Melville`, "",
+ },
+ {"TU9CWS1ESUNLOwoKb3IsIFRIRSBXSEFMRS4KCgpCeSBIZXJtYW4gTWVsdmlsbGU=",
+ "URLAlph",
+ `MOBY-DICK;
+
+or, THE WHALE.
+
+
+By Herman Melville`, "",
+ },
+
+ // Test that a string that doesn't need padding can't have it
+ {"cGFk", "StdAlph", "pad", ""},
+ {"cGFk=", "StdAlph", "pad", "input byte 4"},
+ {"cGFk==", "StdAlph", "pad", "input byte 4"},
+ {"cGFk===", "StdAlph", "pad", "input byte 4"},
+ // Ensures that even correct padding is illegal if not needed
+ {"cGFk====", "StdAlph", "pad", "input byte 4"},
+
+ // Test that padding must be present to make len = 0 mod 4.
+ {"bm9wYWQ=", "StdAlph", "nopad", ""},
+ {"bm9wYWQ", "StdAlph", "nopad", "illegal"},
+ {"bm9wYWQ==", "StdAlph", "nopad", "illegal"},
+
+ {"YWJjMTIzIT8kKiYoKSctPUB+", "StdAlph", "abc123!?$*&()'-=@~", ""},
+ {"YWJjMTIzIT8kKiYoKSctPUB+", "StdAlph", "abc123!?$*&()'-=@~", ""},
+ {"YWJjMTIzIT8kKiYoKSctPUB-", "URLAlph", "abc123!?$*&()'-=@~", ""},
+ {"YWJjMTIzIT8kKiYoKSctPUB+", "URLAlph", "", "input byte 23"},
+ {"YWJjMTIzIT8kKiYoKSctPUB-", "StdAlph", "", "input byte 23"},
+ }
+
+ template := `byte 0x%s; byte "%s"; base64_decode %s; ==`
+ for _, tc := range testCases {
+ source := fmt.Sprintf(template, hex.EncodeToString([]byte(tc.decoded)), tc.encoded, tc.alph)
+
+ if tc.error == "" {
+ testAccepts(t, source, minB64DecodeVersion)
+ } else {
+ err := testPanics(t, source, minB64DecodeVersion)
+ require.Contains(t, err.Error(), tc.error)
+ }
+ }
+}
diff --git a/data/transactions/logic/export_test.go b/data/transactions/logic/export_test.go
new file mode 100644
index 0000000000..407525e4fc
--- /dev/null
+++ b/data/transactions/logic/export_test.go
@@ -0,0 +1,40 @@
+// Copyright (C) 2019-2021 Algorand, Inc.
+// This file is part of go-algorand
+//
+// go-algorand is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// go-algorand is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with go-algorand. If not, see .
+
+package logic
+
+// Export for testing only. See
+// https://medium.com/@robiplus/golang-trick-export-for-test-aa16cbd7b8cd for a
+// nice explanation.
+
+func NewExpect(l int, s string) Expect {
+ return Expect{l, s}
+}
+
+var MakeSampleEnv = makeSampleEnv
+var MakeSampleEnvWithVersion = makeSampleEnvWithVersion
+var MakeSampleTxn = makeSampleTxn
+var MakeSampleTxnGroup = makeSampleTxnGroup
+var MakeTestProto = makeTestProto
+var MakeTestProtoV = makeTestProtoV
+var Obfuscate = obfuscate
+var TestApp = testApp
+var TestAppBytes = testAppBytes
+var TestApps = testApps
+var TestProg = testProg
+
+const InnerAppsEnabledVersion = innerAppsEnabledVersion
+const CreatedResourcesVersion = createdResourcesVersion
diff --git a/data/transactions/logic/fields.go b/data/transactions/logic/fields.go
index da2b26e10d..e15190d77a 100644
--- a/data/transactions/logic/fields.go
+++ b/data/transactions/logic/fields.go
@@ -23,7 +23,7 @@ import (
"github.com/algorand/go-algorand/protocol"
)
-//go:generate stringer -type=TxnField,GlobalField,AssetParamsField,AppParamsField,AssetHoldingField,OnCompletionConstType,EcdsaCurve -output=fields_string.go
+//go:generate stringer -type=TxnField,GlobalField,AssetParamsField,AppParamsField,AssetHoldingField,OnCompletionConstType,EcdsaCurve,Base64Alphabet -output=fields_string.go
// TxnField is an enum type for `txn` and `gtxn`
type TxnField int
@@ -465,6 +465,44 @@ func (s ecDsaCurveNameSpecMap) getExtraFor(name string) (extra string) {
return
}
+// Base64Alphabet is an enum for the `base64decode` opcode
+type Base64Alphabet int
+
+const (
+ // URLAlph represents the base64url alphabet defined in https://www.rfc-editor.org/rfc/rfc4648.html
+ URLAlph Base64Alphabet = iota
+ // StdAlph represents the standard alphabet of the RFC
+ StdAlph
+ invalidBase64Alphabet
+)
+
+// After running `go generate` these strings will be available:
+var base64AlphabetNames [2]string = [...]string{URLAlph.String(), StdAlph.String()}
+
+type base64AlphabetSpec struct {
+ field Base64Alphabet
+ ftype StackType
+ version uint64
+}
+
+var base64AlphbetSpecs = []base64AlphabetSpec{
+ {URLAlph, StackBytes, 6},
+ {StdAlph, StackBytes, 6},
+}
+
+var base64AlphabetSpecByField map[Base64Alphabet]base64AlphabetSpec
+var base64AlphabetSpecByName base64AlphabetSpecMap
+
+type base64AlphabetSpecMap map[string]base64AlphabetSpec
+
+func (s base64AlphabetSpecMap) getExtraFor(name string) (extra string) {
+ // Uses 6 here because base64_decode fields were introduced in 6
+ if s[name].version > 6 {
+ extra = fmt.Sprintf("LogicSigVersion >= %d.", s[name].version)
+ }
+ return
+}
+
// AssetHoldingField is an enum for `asset_holding_get` opcode
type AssetHoldingField int
@@ -698,6 +736,16 @@ func init() {
ecdsaCurveSpecByName[ahfn] = ecdsaCurveSpecByField[EcdsaCurve(i)]
}
+ base64AlphabetSpecByField = make(map[Base64Alphabet]base64AlphabetSpec, len(base64AlphabetNames))
+ for _, s := range base64AlphbetSpecs {
+ base64AlphabetSpecByField[s.field] = s
+ }
+
+ base64AlphabetSpecByName = make(base64AlphabetSpecMap, len(base64AlphabetNames))
+ for i, alphname := range base64AlphabetNames {
+ base64AlphabetSpecByName[alphname] = base64AlphabetSpecByField[Base64Alphabet(i)]
+ }
+
AssetHoldingFieldNames = make([]string, int(invalidAssetHoldingField))
for i := AssetBalance; i < invalidAssetHoldingField; i++ {
AssetHoldingFieldNames[int(i)] = i.String()
diff --git a/data/transactions/logic/fields_string.go b/data/transactions/logic/fields_string.go
index 31012932cd..ae931b2ab6 100644
--- a/data/transactions/logic/fields_string.go
+++ b/data/transactions/logic/fields_string.go
@@ -1,4 +1,4 @@
-// Code generated by "stringer -type=TxnField,GlobalField,AssetParamsField,AppParamsField,AssetHoldingField,OnCompletionConstType,EcdsaCurve -output=fields_string.go"; DO NOT EDIT.
+// Code generated by "stringer -type=TxnField,GlobalField,AssetParamsField,AppParamsField,AssetHoldingField,OnCompletionConstType,EcdsaCurve,Base64Alphabet -output=fields_string.go"; DO NOT EDIT.
package logic
@@ -230,3 +230,22 @@ func (i EcdsaCurve) String() string {
}
return _EcdsaCurve_name[_EcdsaCurve_index[i]:_EcdsaCurve_index[i+1]]
}
+func _() {
+ // An "invalid array index" compiler error signifies that the constant values have changed.
+ // Re-run the stringer command to generate them again.
+ var x [1]struct{}
+ _ = x[URLAlph-0]
+ _ = x[StdAlph-1]
+ _ = x[invalidBase64Alphabet-2]
+}
+
+const _Base64Alphabet_name = "URLAlphStdAlphinvalidBase64Alphabet"
+
+var _Base64Alphabet_index = [...]uint8{0, 7, 14, 35}
+
+func (i Base64Alphabet) String() string {
+ if i < 0 || i >= Base64Alphabet(len(_Base64Alphabet_index)-1) {
+ return "Base64Alphabet(" + strconv.FormatInt(int64(i), 10) + ")"
+ }
+ return _Base64Alphabet_name[_Base64Alphabet_index[i]:_Base64Alphabet_index[i+1]]
+}
diff --git a/data/transactions/logic/fields_test.go b/data/transactions/logic/fields_test.go
index e0c75d20b0..3df38ba6d8 100644
--- a/data/transactions/logic/fields_test.go
+++ b/data/transactions/logic/fields_test.go
@@ -60,16 +60,16 @@ func TestGlobalFieldsVersions(t *testing.T) {
// check on a version before the field version
preLogicVersion := field.version - 1
- proto := defaultEvalProtoWithVersion(preLogicVersion)
+ proto := makeTestProtoV(preLogicVersion)
if preLogicVersion < appsEnabledVersion {
require.False(t, proto.Application)
}
ep := defaultEvalParams(nil)
- ep.Proto = &proto
+ ep.Proto = proto
ep.Ledger = ledger
// check failure with version check
- _, err := EvalApp(ops.Program, 0, ep)
+ _, err := EvalApp(ops.Program, 0, 0, ep)
require.Error(t, err)
require.Contains(t, err.Error(), "greater than protocol supported version")
@@ -138,12 +138,12 @@ func TestTxnFieldVersions(t *testing.T) {
ops := testProg(t, text, AssemblerMaxVersion)
preLogicVersion := fs.version - 1
- proto := defaultEvalProtoWithVersion(preLogicVersion)
+ proto := makeTestProtoV(preLogicVersion)
if preLogicVersion < appsEnabledVersion {
require.False(t, proto.Application)
}
ep := defaultEvalParams(nil)
- ep.Proto = &proto
+ ep.Proto = proto
ep.Ledger = ledger
ep.TxnGroup = transactions.WrapSignedTxnsWithAD(txgroup)
@@ -196,7 +196,7 @@ func TestTxnEffectsAvailable(t *testing.T) {
_, err := EvalSignature(0, ep)
require.Error(t, err)
ep.Ledger = MakeLedger(nil)
- _, err = EvalApp(ops.Program, 0, ep)
+ _, err = EvalApp(ops.Program, 0, 0, ep)
if v < txnEffectsVersion {
require.Error(t, err)
} else {
@@ -230,7 +230,7 @@ func TestAssetParamsFieldsVersions(t *testing.T) {
ep, _, _ := makeSampleEnv()
ep.Proto.LogicSigVersion = v
if field.version > v {
- testProg(t, text, v, expect{3, "...available in version..."})
+ testProg(t, text, v, Expect{3, "...available in version..."})
ops := testProg(t, text, field.version) // assemble in the future
ops.Program[0] = byte(v)
testAppBytes(t, ops.Program, ep, "invalid asset_params_get field")
diff --git a/data/transactions/logic/ledger_test.go b/data/transactions/logic/ledger_test.go
index 460d3c0b47..0769419d55 100644
--- a/data/transactions/logic/ledger_test.go
+++ b/data/transactions/logic/ledger_test.go
@@ -65,7 +65,6 @@ type Ledger struct {
applications map[basics.AppIndex]appParams
assets map[basics.AssetIndex]asaParams
trackedCreatables map[int]basics.CreatableIndex
- appID basics.AppIndex
mods map[basics.AppIndex]map[string]basics.ValueDelta
rnd basics.Round
}
@@ -81,7 +80,6 @@ func MakeLedger(balances map[basics.Address]uint64) *Ledger {
l.assets = make(map[basics.AssetIndex]asaParams)
l.trackedCreatables = make(map[int]basics.CreatableIndex)
l.mods = make(map[basics.AppIndex]map[string]basics.ValueDelta)
- l.SetApp(1234) // Arbitrary initial app id for testApp()
return l
}
@@ -104,7 +102,6 @@ func (l *Ledger) NewAccount(addr basics.Address, balance uint64) {
// up the id and schema but no code, as testing will want to try many different
// code sequences.
func (l *Ledger) NewApp(creator basics.Address, appID basics.AppIndex, params basics.AppParams) {
- l.SetApp(appID)
params = params.Clone()
if params.GlobalState == nil {
params.GlobalState = make(basics.TealKeyValue)
@@ -115,10 +112,6 @@ func (l *Ledger) NewApp(creator basics.Address, appID basics.AppIndex, params ba
}
}
-func (l *Ledger) SetApp(appID basics.AppIndex) {
- l.appID = appID
-}
-
// NewAsset adds an asset with the given id and params to the ledger.
func (l *Ledger) NewAsset(creator basics.Address, assetID basics.AssetIndex, params basics.AssetParams) {
l.assets[assetID] = asaParams{
@@ -133,9 +126,12 @@ func (l *Ledger) NewAsset(creator basics.Address, assetID basics.AssetIndex, par
l.balances[creator] = br
}
-// freshID gets a new creatable ID that isn't in use
-func (l *Ledger) freshID() uint64 {
- for try := l.appID + 1; true; try++ {
+const firstTestID = 5000
+
+// Counter implements LedgerForLogic, but it not really a txn counter, but is
+// sufficient for the logic package.
+func (l *Ledger) Counter() uint64 {
+ for try := firstTestID; true; try++ {
if _, ok := l.assets[basics.AssetIndex(try)]; ok {
continue
}
@@ -265,12 +261,9 @@ func (l *Ledger) Authorizer(addr basics.Address) (basics.Address, error) {
// GetGlobal returns the current value of a global in an app, taking
// into account the mods created by earlier teal execution.
func (l *Ledger) GetGlobal(appIdx basics.AppIndex, key string) (basics.TealValue, bool, error) {
- if appIdx == basics.AppIndex(0) {
- appIdx = l.appID
- }
params, ok := l.applications[appIdx]
if !ok {
- return basics.TealValue{}, false, fmt.Errorf("no such app")
+ return basics.TealValue{}, false, fmt.Errorf("no such app %d", appIdx)
}
// return most recent value if available
@@ -290,11 +283,10 @@ func (l *Ledger) GetGlobal(appIdx basics.AppIndex, key string) (basics.TealValue
// SetGlobal "sets" a global, but only through the mods mechanism, so
// it can be removed with Reset()
-func (l *Ledger) SetGlobal(key string, value basics.TealValue) error {
- appIdx := l.appID
+func (l *Ledger) SetGlobal(appIdx basics.AppIndex, key string, value basics.TealValue) error {
params, ok := l.applications[appIdx]
if !ok {
- return fmt.Errorf("no such app")
+ return fmt.Errorf("no such app %d", appIdx)
}
// if writing the same value, return
@@ -315,11 +307,10 @@ func (l *Ledger) SetGlobal(key string, value basics.TealValue) error {
// DelGlobal "deletes" a global, but only through the mods mechanism, so
// the deletion can be Reset()
-func (l *Ledger) DelGlobal(key string) error {
- appIdx := l.appID
+func (l *Ledger) DelGlobal(appIdx basics.AppIndex, key string) error {
params, ok := l.applications[appIdx]
if !ok {
- return fmt.Errorf("no such app")
+ return fmt.Errorf("no such app %d", appIdx)
}
exist := false
@@ -345,9 +336,6 @@ func (l *Ledger) DelGlobal(key string) error {
// GetLocal returns the current value bound to a local key, taking
// into account mods caused by earlier executions.
func (l *Ledger) GetLocal(addr basics.Address, appIdx basics.AppIndex, key string, accountIdx uint64) (basics.TealValue, bool, error) {
- if appIdx == 0 {
- appIdx = l.appID
- }
br, ok := l.balances[addr]
if !ok {
return basics.TealValue{}, false, fmt.Errorf("no such address")
@@ -373,9 +361,7 @@ func (l *Ledger) GetLocal(addr basics.Address, appIdx basics.AppIndex, key strin
// SetLocal "sets" the current value bound to a local key using the
// mods mechanism, so it can be Reset()
-func (l *Ledger) SetLocal(addr basics.Address, key string, value basics.TealValue, accountIdx uint64) error {
- appIdx := l.appID
-
+func (l *Ledger) SetLocal(addr basics.Address, appIdx basics.AppIndex, key string, value basics.TealValue, accountIdx uint64) error {
br, ok := l.balances[addr]
if !ok {
return fmt.Errorf("no such address")
@@ -403,9 +389,7 @@ func (l *Ledger) SetLocal(addr basics.Address, key string, value basics.TealValu
// DelLocal "deletes" the current value bound to a local key using the
// mods mechanism, so it can be Reset()
-func (l *Ledger) DelLocal(addr basics.Address, key string, accountIdx uint64) error {
- appIdx := l.appID
-
+func (l *Ledger) DelLocal(addr basics.Address, appIdx basics.AppIndex, key string, accountIdx uint64) error {
br, ok := l.balances[addr]
if !ok {
return fmt.Errorf("no such address")
@@ -438,9 +422,6 @@ func (l *Ledger) DelLocal(addr basics.Address, key string, accountIdx uint64) er
// from NewLocals, but potentially from executing AVM inner
// transactions.
func (l *Ledger) OptedIn(addr basics.Address, appIdx basics.AppIndex) (bool, error) {
- if appIdx == 0 {
- appIdx = l.appID
- }
br, ok := l.balances[addr]
if !ok {
return false, fmt.Errorf("no such address")
@@ -486,19 +467,7 @@ func (l *Ledger) AppParams(appID basics.AppIndex) (basics.AppParams, basics.Addr
if app, ok := l.applications[appID]; ok {
return app.AppParams, app.Creator, nil
}
- return basics.AppParams{}, basics.Address{}, fmt.Errorf("no such app")
-}
-
-// ApplicationID gives ID of the "currently running" app. For this
-// test ledger, that is chosen explicitly.
-func (l *Ledger) ApplicationID() basics.AppIndex {
- return l.appID
-}
-
-// CreatorAddress returns of the address that created the "currently running" app.
-func (l *Ledger) CreatorAddress() basics.Address {
- _, addr, _ := l.AppParams(l.appID)
- return addr
+ return basics.AppParams{}, basics.Address{}, fmt.Errorf("no such app %d", appID)
}
func (l *Ledger) move(from basics.Address, to basics.Address, amount uint64) error {
@@ -640,7 +609,7 @@ func (l *Ledger) axfer(from basics.Address, xfer transactions.AssetTransferTxnFi
func (l *Ledger) acfg(from basics.Address, cfg transactions.AssetConfigTxnFields, ad *transactions.ApplyData) error {
if cfg.ConfigAsset == 0 {
- aid := basics.AssetIndex(l.freshID())
+ aid := basics.AssetIndex(l.Counter())
l.NewAsset(from, aid, cfg.AssetParams)
ad.ConfigAsset = aid
return nil
@@ -678,11 +647,9 @@ func (l *Ledger) afrz(from basics.Address, frz transactions.AssetFreezeTxnFields
}
func (l *Ledger) appl(from basics.Address, appl transactions.ApplicationCallTxnFields, ad *transactions.ApplyData, gi int, ep *EvalParams) error {
- // This is just a mock. We don't run it yet, but we always do the implied
- // operation (create, update, delete).
aid := appl.ApplicationID
if aid == 0 {
- aid = basics.AppIndex(l.freshID())
+ aid = basics.AppIndex(l.Counter())
params := basics.AppParams{
ApprovalProgram: appl.ApprovalProgram,
ClearStateProgram: appl.ClearStateProgram,
@@ -699,9 +666,7 @@ func (l *Ledger) appl(from basics.Address, appl transactions.ApplicationCallTxnF
},
ExtraProgramPages: appl.ExtraProgramPages,
}
- was := l.appID
l.NewApp(from, aid, params)
- l.SetApp(was)
ad.ApplicationID = aid
}
@@ -718,7 +683,7 @@ func (l *Ledger) appl(from basics.Address, appl transactions.ApplicationCallTxnF
if !ok {
return errors.New("No application")
}
- pass, cx, err := EvalContract(params.ApprovalProgram, gi, ep)
+ pass, cx, err := EvalContract(params.ApprovalProgram, gi, aid, ep)
if err != nil {
return err
}
diff --git a/data/transactions/logic/opcodes.go b/data/transactions/logic/opcodes.go
index 18feff9546..fe084951de 100644
--- a/data/transactions/logic/opcodes.go
+++ b/data/transactions/logic/opcodes.go
@@ -50,6 +50,11 @@ const innerAppsEnabledVersion = 6
// "effects" (ApplyData info)
const txnEffectsVersion = 6
+// createdResourcesVersion is the first version that allows access to assets and
+// applications that were created in the same group, despite them not being in
+// the Foreign arrays.
+const createdResourcesVersion = 6
+
// opDetails records details such as non-standard costs, immediate
// arguments, or dynamic layout controlled by a check function.
type opDetails struct {
@@ -245,6 +250,7 @@ var OpSpecs = []OpSpec{
{0x4e, "cover", opCover, asmDefault, disDefault, oneAny, oneAny, 5, modeAny, stacky(typeCover, "n")},
{0x4f, "uncover", opUncover, asmDefault, disDefault, oneAny, oneAny, 5, modeAny, stacky(typeUncover, "n")},
+ // byteslice processing / StringOps
{0x50, "concat", opConcat, asmDefault, disDefault, twoBytes, oneBytes, 2, modeAny, opDefault},
{0x51, "substring", opSubstring, assembleSubstring, disDefault, oneBytes, oneBytes, 2, modeAny, immediates("s", "e")},
{0x52, "substring3", opSubstring3, asmDefault, disDefault, byteIntInt, oneBytes, 2, modeAny, opDefault},
@@ -257,6 +263,7 @@ var OpSpecs = []OpSpec{
{0x59, "extract_uint16", opExtract16Bits, asmDefault, disDefault, byteInt, oneInt, 5, modeAny, opDefault},
{0x5a, "extract_uint32", opExtract32Bits, asmDefault, disDefault, byteInt, oneInt, 5, modeAny, opDefault},
{0x5b, "extract_uint64", opExtract64Bits, asmDefault, disDefault, byteInt, oneInt, 5, modeAny, opDefault},
+ {0x5c, "base64_decode", opBase64Decode, assembleBase64Decode, disBase64Decode, oneBytes, oneBytes, 6, modeAny, costlyImm(25, "e")},
{0x60, "balance", opBalance, asmDefault, disDefault, oneInt, oneInt, 2, runModeApplication, opDefault},
{0x60, "balance", opBalance, asmDefault, disDefault, oneAny, oneInt, directRefEnabledVersion, runModeApplication, opDefault},
diff --git a/data/transactions/transaction.go b/data/transactions/transaction.go
index efa6840093..7a39bb20f2 100644
--- a/data/transactions/transaction.go
+++ b/data/transactions/transaction.go
@@ -419,11 +419,11 @@ func (tx Transaction) WellFormed(spec SpecialAddresses, proto config.ConsensusPa
// Limit the sum of all types of references that bring in account records
if len(tx.Accounts)+len(tx.ForeignApps)+len(tx.ForeignAssets) > proto.MaxAppTotalTxnReferences {
- return fmt.Errorf("tx has too many references, max is %d", proto.MaxAppTotalTxnReferences)
+ return fmt.Errorf("tx references exceed MaxAppTotalTxnReferences = %d", proto.MaxAppTotalTxnReferences)
}
if tx.ExtraProgramPages > uint32(proto.MaxExtraAppProgramPages) {
- return fmt.Errorf("tx.ExtraProgramPages too large, max number of extra pages is %d", proto.MaxExtraAppProgramPages)
+ return fmt.Errorf("tx.ExtraProgramPages exceeds MaxExtraAppProgramPages = %d", proto.MaxExtraAppProgramPages)
}
lap := len(tx.ApprovalProgram)
diff --git a/data/transactions/transaction_test.go b/data/transactions/transaction_test.go
index 72fe13b2f6..3709214402 100644
--- a/data/transactions/transaction_test.go
+++ b/data/transactions/transaction_test.go
@@ -305,7 +305,7 @@ func TestWellFormedErrors(t *testing.T) {
},
spec: specialAddr,
proto: protoV27,
- expectedError: fmt.Errorf("tx.ExtraProgramPages too large, max number of extra pages is %d", protoV27.MaxExtraAppProgramPages),
+ expectedError: fmt.Errorf("tx.ExtraProgramPages exceeds MaxExtraAppProgramPages = %d", protoV27.MaxExtraAppProgramPages),
},
{
tx: Transaction{
@@ -392,7 +392,7 @@ func TestWellFormedErrors(t *testing.T) {
},
spec: specialAddr,
proto: futureProto,
- expectedError: fmt.Errorf("tx.ExtraProgramPages too large, max number of extra pages is %d", futureProto.MaxExtraAppProgramPages),
+ expectedError: fmt.Errorf("tx.ExtraProgramPages exceeds MaxExtraAppProgramPages = %d", futureProto.MaxExtraAppProgramPages),
},
{
tx: Transaction{
@@ -457,7 +457,7 @@ func TestWellFormedErrors(t *testing.T) {
},
spec: specialAddr,
proto: futureProto,
- expectedError: fmt.Errorf("tx has too many references, max is 8"),
+ expectedError: fmt.Errorf("tx references exceed MaxAppTotalTxnReferences = 8"),
},
{
tx: Transaction{
diff --git a/go.mod b/go.mod
index 00a9719d63..5c3ee9295b 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
module github.com/algorand/go-algorand
-go 1.14
+go 1.16
require (
github.com/algorand/go-codec/codec v0.0.0-20190507210007-269d70b6135d
@@ -42,7 +42,7 @@ require (
github.com/sirupsen/logrus v1.4.2
github.com/spf13/cobra v0.0.3
github.com/spf13/pflag v1.0.5 // indirect
- github.com/stretchr/testify v1.6.1
+ github.com/stretchr/testify v1.7.0
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a
golang.org/x/net v0.0.0-20200904194848-62affa334b73
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 // indirect
diff --git a/go.sum b/go.sum
index 0ac28a9455..80be32d034 100644
--- a/go.sum
+++ b/go.sum
@@ -20,7 +20,6 @@ github.com/chrismcguire/gobberish v0.0.0-20150821175641-1d8adb509a0e h1:CHPYEbz7
github.com/chrismcguire/gobberish v0.0.0-20150821175641-1d8adb509a0e/go.mod h1:6Xhs0ZlsRjXLIiSMLKafbZxML/j30pg9Z1priLuha5s=
github.com/cpuguy83/go-md2man v1.0.8 h1:DwoNytLphI8hzS2Af4D0dfaEaiSq2bN05mEm4R6vf8M=
github.com/cpuguy83/go-md2man v1.0.8/go.mod h1:N6JayAiVKtlHSnuTCeuLSQVs75hb8q+dYQLjr7cDsKY=
-github.com/cyberdelia/templates v0.0.0-20191230040416-20a325f050d4 h1:Fphwr1XDjkTR/KFbrrkLfY6D2CEOlHqFGomQQrxcHFs=
github.com/cyberdelia/templates v0.0.0-20191230040416-20a325f050d4/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -42,7 +41,6 @@ github.com/getkin/kin-openapi v0.22.0 h1:J5IFyKd/5yuB6AZAgwK0CMBKnabWcmkowtsl6bR
github.com/getkin/kin-openapi v0.22.0/go.mod h1:WGRs2ZMM1Q8LR1QBEwUxC6RJEfaBcD0s+pcEVXFuAjw=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
-github.com/go-chi/chi v4.1.1+incompatible h1:MmTgB0R8Bt/jccxp+t6S/1VGIKdJw5J74CK/c9tTfA4=
github.com/go-chi/chi v4.1.1+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx+opk=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
@@ -50,7 +48,6 @@ github.com/godbus/dbus v0.0.0-20181101234600-2ff6f7ffd60f h1:zlOR3rOlPAVvtfuxGKo
github.com/godbus/dbus v0.0.0-20181101234600-2ff6f7ffd60f/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
github.com/gofrs/flock v0.7.0 h1:pGFUjl501gafK9HBt1VGL1KCOd/YhIooID+xgyJCf3g=
github.com/gofrs/flock v0.7.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
-github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y=
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
@@ -64,8 +61,6 @@ github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
-github.com/gorilla/schema v1.0.2 h1:sAgNfOcNYvdDSrzGHVy9nzCQahG+qmsg+nE8dK85QRA=
-github.com/gorilla/schema v1.0.2/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
@@ -84,7 +79,6 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
-github.com/labstack/echo/v4 v4.1.16 h1:8swiwjE5Jkai3RPfZoahp8kjVCRNq+y7Q0hPji2Kz0o=
github.com/labstack/echo/v4 v4.1.16/go.mod h1:awO+5TzAjvL8XpibdsfXxPgHr+orhtXZJZIQCVjogKI=
github.com/labstack/echo/v4 v4.1.17 h1:PQIBaRplyRy3OjwILGkPg89JRtH2x5bssi59G2EL3fo=
github.com/labstack/echo/v4 v4.1.17/go.mod h1:Tn2yRQL/UclUalpb5rPdXDevbkJ+lp/2svdyFBg6CHQ=
@@ -94,10 +88,8 @@ github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
-github.com/matryer/moq v0.0.0-20200310130814-7721994d1b54 h1:p8zN0Xu28xyEkPpqLbFXAnjdgBVvTJCpfOtoDf/+/RQ=
github.com/matryer/moq v0.0.0-20200310130814-7721994d1b54/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
-github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw=
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
@@ -138,16 +130,16 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
-github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31 h1:OXcKh35JaYsGMRzpvFkLv/MEyPuL49CThT1pZ8aSml4=
github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
-github.com/valyala/fasttemplate v1.1.0 h1:RZqt0yGBsps8NGvLSGW804QQqCUYYLsaOjTVHy1Ocw4=
github.com/valyala/fasttemplate v1.1.0/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4=
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
@@ -160,7 +152,6 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM=
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
-golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
@@ -186,12 +177,9 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f h1:Fqb3ao1hUmOR3GkUOg/Y+BadLwykBIzs5q8Ez2SbHyc=
-golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -199,11 +187,9 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200423205358-59e73619c742 h1:9OGWpORUXvk8AsaBJlpzzDx7Srv/rSK6rvjcsJq4rJo=
golang.org/x/tools v0.0.0-20200423205358-59e73619c742/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
diff --git a/ledger/applications_test.go b/ledger/applications_test.go
index 7768329689..5630fc19c4 100644
--- a/ledger/applications_test.go
+++ b/ledger/applications_test.go
@@ -34,9 +34,23 @@ import (
)
func commitRound(offset uint64, dbRound basics.Round, l *Ledger) {
+ l.trackers.mu.Lock()
l.trackers.lastFlushTime = time.Time{}
+ l.trackers.mu.Unlock()
+
l.trackers.scheduleCommit(l.Latest(), l.Latest()-(dbRound+basics.Round(offset)))
- l.trackers.waitAccountsWriting()
+ // wait for the operation to complete. Once it does complete, the tr.lastFlushTime is going to be updated, so we can
+ // use that as an indicator.
+ for {
+ l.trackers.mu.Lock()
+ isDone := (!l.trackers.lastFlushTime.IsZero()) && (len(l.trackers.deferredCommits) == 0)
+ l.trackers.mu.Unlock()
+ if isDone {
+ break
+ }
+ time.Sleep(time.Millisecond)
+
+ }
}
// test ensures that
diff --git a/ledger/apply/application.go b/ledger/apply/application.go
index f7d82f224b..a178723e38 100644
--- a/ledger/apply/application.go
+++ b/ledger/apply/application.go
@@ -297,13 +297,13 @@ func closeOutApplication(balances Balances, sender basics.Address, appIdx basics
return nil
}
-func checkPrograms(ac *transactions.ApplicationCallTxnFields, gi int, evalParams *logic.EvalParams) error {
- err := logic.CheckContract(ac.ApprovalProgram, gi, evalParams)
+func checkPrograms(ac *transactions.ApplicationCallTxnFields, evalParams *logic.EvalParams) error {
+ err := logic.CheckContract(ac.ApprovalProgram, evalParams)
if err != nil {
return fmt.Errorf("check failed on ApprovalProgram: %v", err)
}
- err = logic.CheckContract(ac.ClearStateProgram, gi, evalParams)
+ err = logic.CheckContract(ac.ClearStateProgram, evalParams)
if err != nil {
return fmt.Errorf("check failed on ClearStateProgram: %v", err)
}
@@ -364,7 +364,7 @@ func ApplicationCall(ac transactions.ApplicationCallTxnFields, header transactio
// If this txn is going to set new programs (either for creation or
// update), check that the programs are valid and not too expensive
if ac.ApplicationID == 0 || ac.OnCompletion == transactions.UpdateApplicationOC {
- err = checkPrograms(&ac, gi, evalParams)
+ err = checkPrograms(&ac, evalParams)
if err != nil {
return err
}
diff --git a/ledger/apply/application_test.go b/ledger/apply/application_test.go
index ae1ec23cfd..2e830f8c6f 100644
--- a/ledger/apply/application_test.go
+++ b/ledger/apply/application_test.go
@@ -399,7 +399,7 @@ func TestAppCallCheckPrograms(t *testing.T) {
ep.Proto = &proto
proto.MaxAppProgramCost = 1
- err := checkPrograms(&ac, 0, &ep)
+ err := checkPrograms(&ac, &ep)
a.Error(err)
a.Contains(err.Error(), "check failed on ApprovalProgram")
@@ -407,23 +407,23 @@ func TestAppCallCheckPrograms(t *testing.T) {
ac.ApprovalProgram = program
ac.ClearStateProgram = program
- err = checkPrograms(&ac, 0, &ep)
+ err = checkPrograms(&ac, &ep)
a.Error(err)
a.Contains(err.Error(), "check failed on ApprovalProgram")
proto.MaxAppProgramCost = 10
- err = checkPrograms(&ac, 0, &ep)
+ err = checkPrograms(&ac, &ep)
a.NoError(err)
ac.ClearStateProgram = append(ac.ClearStateProgram, program...)
ac.ClearStateProgram = append(ac.ClearStateProgram, program...)
ac.ClearStateProgram = append(ac.ClearStateProgram, program...)
- err = checkPrograms(&ac, 0, &ep)
+ err = checkPrograms(&ac, &ep)
a.Error(err)
a.Contains(err.Error(), "check failed on ClearStateProgram")
ac.ClearStateProgram = program
- err = checkPrograms(&ac, 0, &ep)
+ err = checkPrograms(&ac, &ep)
a.NoError(err)
}
diff --git a/ledger/internal/appcow.go b/ledger/internal/appcow.go
index 2343767243..012278bf30 100644
--- a/ledger/internal/appcow.go
+++ b/ledger/internal/appcow.go
@@ -477,13 +477,10 @@ func MakeDebugBalances(l LedgerForCowBase, round basics.Round, proto protocol.Co
func (cb *roundCowState) StatefulEval(gi int, params *logic.EvalParams, aidx basics.AppIndex, program []byte) (pass bool, evalDelta transactions.EvalDelta, err error) {
// Make a child cow to eval our program in
calf := cb.child(1)
- params.Ledger, err = newLogicLedger(calf, aidx)
- if err != nil {
- return false, transactions.EvalDelta{}, err
- }
+ params.Ledger = newLogicLedger(calf)
// Eval the program
- pass, cx, err := logic.EvalContract(program, gi, params)
+ pass, cx, err := logic.EvalContract(program, gi, aidx, params)
if err != nil {
var details string
if cx != nil {
diff --git a/ledger/internal/applications.go b/ledger/internal/applications.go
index b0106f5e49..962e8a18c4 100644
--- a/ledger/internal/applications.go
+++ b/ledger/internal/applications.go
@@ -28,9 +28,7 @@ import (
)
type logicLedger struct {
- aidx basics.AppIndex
- creator basics.Address
- cow cowForLogicLedger
+ cow cowForLogicLedger
}
type cowForLogicLedger interface {
@@ -50,25 +48,10 @@ type cowForLogicLedger interface {
incTxnCount()
}
-func newLogicLedger(cow cowForLogicLedger, aidx basics.AppIndex) (*logicLedger, error) {
- if aidx == basics.AppIndex(0) {
- return nil, fmt.Errorf("cannot make logic ledger for app index 0")
+func newLogicLedger(cow cowForLogicLedger) *logicLedger {
+ return &logicLedger{
+ cow: cow,
}
-
- al := &logicLedger{
- aidx: aidx,
- cow: cow,
- }
-
- // Fetch app creator so we don't have to look it up every time we get/set/del
- // a key for this app's global state
- creator, err := al.fetchAppCreator(al.aidx)
- if err != nil {
- return nil, err
- }
- al.creator = creator
-
- return al, nil
}
func (al *logicLedger) Balance(addr basics.Address) (res basics.MicroAlgos, err error) {
@@ -186,34 +169,20 @@ func (al *logicLedger) LatestTimestamp() int64 {
return al.cow.prevTimestamp()
}
-func (al *logicLedger) ApplicationID() basics.AppIndex {
- return al.aidx
-}
-
-func (al *logicLedger) CreatorAddress() basics.Address {
- return al.creator
-}
-
func (al *logicLedger) OptedIn(addr basics.Address, appIdx basics.AppIndex) (bool, error) {
- if appIdx == basics.AppIndex(0) {
- appIdx = al.aidx
- }
return al.cow.allocated(addr, appIdx, false)
}
func (al *logicLedger) GetLocal(addr basics.Address, appIdx basics.AppIndex, key string, accountIdx uint64) (basics.TealValue, bool, error) {
- if appIdx == basics.AppIndex(0) {
- appIdx = al.aidx
- }
return al.cow.GetKey(addr, appIdx, false, key, accountIdx)
}
-func (al *logicLedger) SetLocal(addr basics.Address, key string, value basics.TealValue, accountIdx uint64) error {
- return al.cow.SetKey(addr, al.aidx, false, key, value, accountIdx)
+func (al *logicLedger) SetLocal(addr basics.Address, appIdx basics.AppIndex, key string, value basics.TealValue, accountIdx uint64) error {
+ return al.cow.SetKey(addr, appIdx, false, key, value, accountIdx)
}
-func (al *logicLedger) DelLocal(addr basics.Address, key string, accountIdx uint64) error {
- return al.cow.DelKey(addr, al.aidx, false, key, accountIdx)
+func (al *logicLedger) DelLocal(addr basics.Address, appIdx basics.AppIndex, key string, accountIdx uint64) error {
+ return al.cow.DelKey(addr, appIdx, false, key, accountIdx)
}
func (al *logicLedger) fetchAppCreator(appIdx basics.AppIndex) (basics.Address, error) {
@@ -230,9 +199,6 @@ func (al *logicLedger) fetchAppCreator(appIdx basics.AppIndex) (basics.Address,
}
func (al *logicLedger) GetGlobal(appIdx basics.AppIndex, key string) (basics.TealValue, bool, error) {
- if appIdx == basics.AppIndex(0) {
- appIdx = al.aidx
- }
addr, err := al.fetchAppCreator(appIdx)
if err != nil {
return basics.TealValue{}, false, err
@@ -240,12 +206,20 @@ func (al *logicLedger) GetGlobal(appIdx basics.AppIndex, key string) (basics.Tea
return al.cow.GetKey(addr, appIdx, true, key, 0)
}
-func (al *logicLedger) SetGlobal(key string, value basics.TealValue) error {
- return al.cow.SetKey(al.creator, al.aidx, true, key, value, 0)
+func (al *logicLedger) SetGlobal(appIdx basics.AppIndex, key string, value basics.TealValue) error {
+ creator, err := al.fetchAppCreator(appIdx)
+ if err != nil {
+ return err
+ }
+ return al.cow.SetKey(creator, appIdx, true, key, value, 0)
}
-func (al *logicLedger) DelGlobal(key string) error {
- return al.cow.DelKey(al.creator, al.aidx, true, key, 0)
+func (al *logicLedger) DelGlobal(appIdx basics.AppIndex, key string) error {
+ creator, err := al.fetchAppCreator(appIdx)
+ if err != nil {
+ return err
+ }
+ return al.cow.DelKey(creator, appIdx, true, key, 0)
}
func (al *logicLedger) balances() (apply.Balances, error) {
@@ -323,3 +297,7 @@ func (al *logicLedger) Perform(gi int, ep *logic.EvalParams) error {
return nil
}
+
+func (al *logicLedger) Counter() uint64 {
+ return al.cow.txnCounter()
+}
diff --git a/ledger/internal/applications_test.go b/ledger/internal/applications_test.go
index 94efcef1aa..cc10166976 100644
--- a/ledger/internal/applications_test.go
+++ b/ledger/internal/applications_test.go
@@ -132,27 +132,9 @@ func TestLogicLedgerMake(t *testing.T) {
a := require.New(t)
- _, err := newLogicLedger(nil, 0)
- a.Error(err)
- a.Contains(err.Error(), "cannot make logic ledger for app index 0")
-
- addr := ledgertesting.RandomAddress()
- aidx := basics.AppIndex(1)
-
c := &mockCowForLogicLedger{}
- _, err = newLogicLedger(c, 0)
- a.Error(err)
- a.Contains(err.Error(), "cannot make logic ledger for app index 0")
-
- _, err = newLogicLedger(c, aidx)
- a.Error(err)
- a.Contains(err.Error(), fmt.Sprintf("app %d does not exist", aidx))
-
- c = newCowMock([]modsData{{addr, basics.CreatableIndex(aidx), basics.AppCreatable}})
- l, err := newLogicLedger(c, aidx)
- a.NoError(err)
+ l := newLogicLedger(c)
a.NotNil(l)
- a.Equal(aidx, l.aidx)
a.Equal(c, l.cow)
}
@@ -161,11 +143,8 @@ func TestLogicLedgerBalances(t *testing.T) {
a := require.New(t)
- addr := ledgertesting.RandomAddress()
- aidx := basics.AppIndex(1)
- c := newCowMock([]modsData{{addr, basics.CreatableIndex(aidx), basics.AppCreatable}})
- l, err := newLogicLedger(c, aidx)
- a.NoError(err)
+ c := newCowMock(nil)
+ l := newLogicLedger(c)
a.NotNil(l)
addr1 := ledgertesting.RandomAddress()
@@ -184,8 +163,7 @@ func TestLogicLedgerGetters(t *testing.T) {
addr := ledgertesting.RandomAddress()
aidx := basics.AppIndex(1)
c := newCowMock([]modsData{{addr, basics.CreatableIndex(aidx), basics.AppCreatable}})
- l, err := newLogicLedger(c, aidx)
- a.NoError(err)
+ l := newLogicLedger(c)
a.NotNil(l)
round := basics.Round(1234)
@@ -195,12 +173,9 @@ func TestLogicLedgerGetters(t *testing.T) {
addr1 := ledgertesting.RandomAddress()
c.stores = map[storeLocator]basics.TealKeyValue{{addr1, aidx, false}: {}}
- a.Equal(aidx, l.ApplicationID())
a.Equal(round, l.Round())
a.Equal(ts, l.LatestTimestamp())
- a.True(l.OptedIn(addr1, 0))
a.True(l.OptedIn(addr1, aidx))
- a.False(l.OptedIn(addr, 0))
a.False(l.OptedIn(addr, aidx))
}
@@ -217,11 +192,10 @@ func TestLogicLedgerAsset(t *testing.T) {
{addr, basics.CreatableIndex(aidx), basics.AppCreatable},
{addr1, basics.CreatableIndex(assetIdx), basics.AssetCreatable},
})
- l, err := newLogicLedger(c, aidx)
- a.NoError(err)
+ l := newLogicLedger(c)
a.NotNil(l)
- _, _, err = l.AssetParams(basics.AssetIndex(aidx))
+ _, _, err := l.AssetParams(basics.AssetIndex(aidx))
a.Error(err)
a.Contains(err.Error(), fmt.Sprintf("asset %d does not exist", aidx))
@@ -263,8 +237,7 @@ func TestLogicLedgerGetKey(t *testing.T) {
{addr, basics.CreatableIndex(aidx), basics.AppCreatable},
{addr1, basics.CreatableIndex(assetIdx), basics.AssetCreatable},
})
- l, err := newLogicLedger(c, aidx)
- a.NoError(err)
+ l := newLogicLedger(c)
a.NotNil(l)
_, ok, err := l.GetGlobal(basics.AppIndex(assetIdx), "gkey")
@@ -303,23 +276,22 @@ func TestLogicLedgerSetKey(t *testing.T) {
c := newCowMock([]modsData{
{addr, basics.CreatableIndex(aidx), basics.AppCreatable},
})
- l, err := newLogicLedger(c, aidx)
- a.NoError(err)
+ l := newLogicLedger(c)
a.NotNil(l)
tv := basics.TealValue{Type: basics.TealUintType, Uint: 1}
- err = l.SetGlobal("gkey", tv)
+ err := l.SetGlobal(aidx, "gkey", tv)
a.Error(err)
a.Contains(err.Error(), fmt.Sprintf("no store for (%s %d %v) in mock cow", addr, aidx, true))
tv2 := basics.TealValue{Type: basics.TealUintType, Uint: 2}
c.stores = map[storeLocator]basics.TealKeyValue{{addr, aidx, true}: {"gkey": tv}}
- err = l.SetGlobal("gkey", tv2)
+ err = l.SetGlobal(aidx, "gkey", tv2)
a.NoError(err)
// check local
c.stores = map[storeLocator]basics.TealKeyValue{{addr, aidx, false}: {"lkey": tv}}
- err = l.SetLocal(addr, "lkey", tv2, 0)
+ err = l.SetLocal(addr, aidx, "lkey", tv2, 0)
a.NoError(err)
}
@@ -333,21 +305,20 @@ func TestLogicLedgerDelKey(t *testing.T) {
c := newCowMock([]modsData{
{addr, basics.CreatableIndex(aidx), basics.AppCreatable},
})
- l, err := newLogicLedger(c, aidx)
- a.NoError(err)
+ l := newLogicLedger(c)
a.NotNil(l)
- err = l.DelGlobal("gkey")
+ err := l.DelGlobal(aidx, "gkey")
a.Error(err)
a.Contains(err.Error(), fmt.Sprintf("no store for (%s %d %v) in mock cow", addr, aidx, true))
tv := basics.TealValue{Type: basics.TealUintType, Uint: 1}
c.stores = map[storeLocator]basics.TealKeyValue{{addr, aidx, true}: {"gkey": tv}}
- err = l.DelGlobal("gkey")
+ err = l.DelGlobal(aidx, "gkey")
a.NoError(err)
addr1 := ledgertesting.RandomAddress()
c.stores = map[storeLocator]basics.TealKeyValue{{addr1, aidx, false}: {"lkey": tv}}
- err = l.DelLocal(addr1, "lkey", 0)
+ err = l.DelLocal(addr1, aidx, "lkey", 0)
a.NoError(err)
}
diff --git a/ledger/internal/apptxn_test.go b/ledger/internal/apptxn_test.go
index 43a6fda7aa..1acd5d2906 100644
--- a/ledger/internal/apptxn_test.go
+++ b/ledger/internal/apptxn_test.go
@@ -18,6 +18,7 @@ package internal_test
import (
"fmt"
+ "strings"
"testing"
"github.com/stretchr/testify/require"
@@ -40,10 +41,10 @@ import (
// then approves, if the source avoids panicing and leaves the stack
// empty.
func main(source string) string {
- return fmt.Sprintf(`txn ApplicationID
+ return strings.Replace(fmt.Sprintf(`txn ApplicationID
bz end
%s
- end: int 1`, source)
+ end: int 1`, source), ";", "\n", -1)
}
// TestPayAction ensures a pay in teal affects balances
@@ -1383,3 +1384,119 @@ next2:
Bytes: "A",
}, inner.EvalDelta.GlobalDelta["X"])
}
+
+// TestCreateAndUse checks that an ASA can be created in an early tx, and then
+// used in a later app call tx (in the same group). This was not allowed until
+// v6, because of the strict adherence to the foreign-arrays rules.
+func TestCreateAndUse(t *testing.T) {
+ genBalances, addrs, _ := ledgertesting.NewTestGenesis()
+ l := newTestLedger(t, genBalances)
+ defer l.Close()
+
+ createapp := txntest.Txn{
+ Type: "appl",
+ Sender: addrs[0],
+ ApprovalProgram: main(`
+ itxn_begin
+ int axfer; itxn_field TypeEnum
+ int 0; itxn_field Amount
+ gaid 0; itxn_field XferAsset
+ global CurrentApplicationAddress; itxn_field Sender
+ global CurrentApplicationAddress; itxn_field AssetReceiver
+ itxn_submit
+`),
+ }
+ appIndex := basics.AppIndex(1)
+
+ fund := txntest.Txn{
+ Type: "pay",
+ Sender: addrs[0],
+ Receiver: appIndex.Address(),
+ Amount: 1_000_000,
+ }
+
+ createasa := txntest.Txn{
+ Type: "acfg",
+ Sender: addrs[0],
+ AssetParams: basics.AssetParams{
+ Total: 1000000,
+ Decimals: 3,
+ UnitName: "oz",
+ AssetName: "Gold",
+ URL: "https://gold.rush/",
+ },
+ }
+ asaIndex := basics.AssetIndex(3)
+
+ use := txntest.Txn{
+ Type: "appl",
+ Sender: addrs[0],
+ ApplicationID: basics.AppIndex(1),
+ // The point of this test is to show the following (psychic) setting is unnecessary.
+ //ForeignAssets: []basics.AssetIndex{asaIndex},
+ }
+
+ eval := nextBlock(t, l, true, nil)
+ txn(t, l, eval, &createapp)
+ txn(t, l, eval, &fund)
+ err := txgroup(t, l, eval, &createasa, &use)
+ require.NoError(t, err)
+ vb := endBlock(t, l, eval)
+
+ require.Equal(t, appIndex, vb.Block().Payset[0].ApplyData.ApplicationID)
+ require.Equal(t, asaIndex, vb.Block().Payset[2].ApplyData.ConfigAsset)
+}
+
+func TestGtxnEffects(t *testing.T) {
+ genBalances, addrs, _ := ledgertesting.NewTestGenesis()
+ l := newTestLedger(t, genBalances)
+ defer l.Close()
+
+ createapp := txntest.Txn{
+ Type: "appl",
+ Sender: addrs[0],
+ ApprovalProgram: main(`
+ gtxn 0 CreatedAssetID
+ int 3
+ ==
+ assert
+`),
+ }
+ appIndex := basics.AppIndex(1)
+
+ fund := txntest.Txn{
+ Type: "pay",
+ Sender: addrs[0],
+ Receiver: appIndex.Address(),
+ Amount: 1_000_000,
+ }
+
+ eval := nextBlock(t, l, true, nil)
+ txns(t, l, eval, &createapp, &fund)
+
+ createasa := txntest.Txn{
+ Type: "acfg",
+ Sender: addrs[0],
+ AssetParams: basics.AssetParams{
+ Total: 1000000,
+ Decimals: 3,
+ UnitName: "oz",
+ AssetName: "Gold",
+ URL: "https://gold.rush/",
+ },
+ }
+ asaIndex := basics.AssetIndex(3)
+
+ see := txntest.Txn{
+ Type: "appl",
+ Sender: addrs[0],
+ ApplicationID: basics.AppIndex(1),
+ }
+
+ err := txgroup(t, l, eval, &createasa, &see)
+ require.NoError(t, err)
+ vb := endBlock(t, l, eval)
+
+ require.Equal(t, appIndex, vb.Block().Payset[0].ApplyData.ApplicationID)
+ require.Equal(t, asaIndex, vb.Block().Payset[2].ApplyData.ConfigAsset)
+}
diff --git a/ledger/internal/eval.go b/ledger/internal/eval.go
index 333d34e9b6..225ae227aa 100644
--- a/ledger/internal/eval.go
+++ b/ledger/internal/eval.go
@@ -744,7 +744,7 @@ func (eval *BlockEvaluator) transactionGroup(txgroup []transactions.SignedTxnWit
var groupTxBytes int
cow := eval.state.child(len(txgroup))
- evalParams := logic.NewAppEvalParams(txgroup, &eval.proto, &eval.specials, nil)
+ evalParams := logic.NewAppEvalParams(txgroup, &eval.proto, &eval.specials, cow.txnCounter())
// Evaluate each transaction in the group
txibs = make([]transactions.SignedTxnInBlock, 0, len(txgroup))
@@ -975,6 +975,9 @@ func (eval *BlockEvaluator) applyTransaction(tx transactions.Transaction, balanc
ad.ReceiverRewards = basics.MicroAlgos{}
ad.CloseRewards = basics.MicroAlgos{}
}
+ if evalParams != nil {
+ evalParams.TxnGroup[gi].ApplyData = ad
+ }
return
}
@@ -1357,7 +1360,7 @@ transactionGroupLoop:
if !ok {
break transactionGroupLoop
} else if txgroup.err != nil {
- return ledgercore.StateDelta{}, err
+ return ledgercore.StateDelta{}, txgroup.err
}
for _, br := range txgroup.balances {
diff --git a/libgoal/libgoal.go b/libgoal/libgoal.go
index b7f8cf519c..2563adf6ee 100644
--- a/libgoal/libgoal.go
+++ b/libgoal/libgoal.go
@@ -1037,17 +1037,24 @@ func MakeDryrunState(client Client, txnOrStxn interface{}, otherTxns []transacti
}
// MakeDryrunStateGenerated function creates generatedV2.DryrunRequest data structure
-func MakeDryrunStateGenerated(client Client, txnOrStxn interface{}, otherTxns []transactions.SignedTxn, otherAccts []basics.Address, proto string) (dr generatedV2.DryrunRequest, err error) {
+func MakeDryrunStateGenerated(client Client, txnOrStxnOrSlice interface{}, otherTxns []transactions.SignedTxn, otherAccts []basics.Address, proto string) (dr generatedV2.DryrunRequest, err error) {
var txns []transactions.SignedTxn
- if txnOrStxn == nil {
- // empty input do nothing
- } else if txn, ok := txnOrStxn.(transactions.Transaction); ok {
- txns = append(txns, transactions.SignedTxn{Txn: txn})
- } else if stxn, ok := txnOrStxn.(transactions.SignedTxn); ok {
- txns = append(txns, stxn)
- } else {
- err = fmt.Errorf("unsupported txn type")
- return
+ if txnOrStxnOrSlice != nil {
+ switch txnType := txnOrStxnOrSlice.(type) {
+ case transactions.Transaction:
+ txns = append(txns, transactions.SignedTxn{Txn: txnType})
+ case []transactions.Transaction:
+ for _, t := range txnType {
+ txns = append(txns, transactions.SignedTxn{Txn: t})
+ }
+ case transactions.SignedTxn:
+ txns = append(txns, txnType)
+ case []transactions.SignedTxn:
+ txns = append(txns, txnType...)
+ default:
+ err = fmt.Errorf("unsupported txn type")
+ return
+ }
}
txns = append(txns, otherTxns...)
diff --git a/netdeploy/networkTemplate.go b/netdeploy/networkTemplate.go
index ecc7affdc2..851644802b 100644
--- a/netdeploy/networkTemplate.go
+++ b/netdeploy/networkTemplate.go
@@ -29,6 +29,7 @@ import (
"github.com/algorand/go-algorand/config"
"github.com/algorand/go-algorand/crypto"
+ "github.com/algorand/go-algorand/data/bookkeeping"
"github.com/algorand/go-algorand/gen"
"github.com/algorand/go-algorand/libgoal"
"github.com/algorand/go-algorand/netdeploy/remote"
@@ -59,13 +60,13 @@ func (t NetworkTemplate) createNodeDirectories(targetFolder string, binDir strin
genesisFile := filepath.Join(targetFolder, genesisFileName)
nodeDirs = make(map[string]string)
- getGenesisVerCmd := filepath.Join(binDir, "algod")
importKeysCmd := filepath.Join(binDir, "goal")
- genesisVer, _, err := util.ExecAndCaptureOutput(getGenesisVerCmd, "-G", "-d", targetFolder)
+
+ genesis, err := bookkeeping.LoadGenesisFromFile(filepath.Join(targetFolder, "genesis.json"))
if err != nil {
return
}
- genesisVer = strings.TrimSpace(genesisVer)
+ genesisVer := genesis.ID()
relaysCount := countRelayNodes(t.Nodes)
diff --git a/protocol/hash.go b/protocol/hash.go
index e171146795..77d1a2b2f6 100644
--- a/protocol/hash.go
+++ b/protocol/hash.go
@@ -23,7 +23,15 @@ type HashID string
// Hash IDs for specific object types, in lexicographic order.
// Hash IDs must be PREFIX-FREE (no hash ID is a prefix of another).
const (
- AppIndex HashID = "appID"
+ AppIndex HashID = "appID"
+
+ // ARCReserved is used to reserve prefixes starting with `arc` to
+ // ARCs-related hashes https://github.com/algorandfoundation/ARCs
+ // The prefix for ARC-XXXX should start with:
+ // "arcXXXX" (where "XXXX" is the 0-padded number of the ARC)
+ // For example ARC-0003 can use any prefix starting with "arc0003"
+ ARCReserved HashID = "arc"
+
AuctionBid HashID = "aB"
AuctionDeposit HashID = "aD"
AuctionOutcomes HashID = "aO"
diff --git a/rpcs/blockService.go b/rpcs/blockService.go
index 2e77aba8e5..698751b257 100644
--- a/rpcs/blockService.go
+++ b/rpcs/blockService.go
@@ -19,6 +19,7 @@ package rpcs
import (
"context"
"encoding/binary"
+ "errors"
"net/http"
"path"
"strconv"
@@ -29,6 +30,8 @@ import (
"github.com/algorand/go-codec/codec"
+ "github.com/algorand/go-deadlock"
+
"github.com/algorand/go-algorand/agreement"
"github.com/algorand/go-algorand/config"
"github.com/algorand/go-algorand/crypto"
@@ -61,6 +64,8 @@ const (
BlockAndCertValue = "blockAndCert" // block+cert request data (as the value of requestDataTypeKey)
)
+var errBlockServiceClosed = errors.New("block service is shutting down")
+
// BlockService represents the Block RPC API
type BlockService struct {
ledger *data.Ledger
@@ -74,6 +79,7 @@ type BlockService struct {
enableArchiverFallback bool
log logging.Logger
closeWaitGroup sync.WaitGroup
+ mu deadlock.Mutex
}
// EncodedBlockCert defines how GetBlockBytes encodes a block and its certificate
@@ -118,6 +124,8 @@ func MakeBlockService(log logging.Logger, config config.Local, ledger *data.Ledg
// Start listening to catchup requests over ws
func (bs *BlockService) Start() {
+ bs.mu.Lock()
+ defer bs.mu.Unlock()
if bs.enableServiceOverGossip {
handlers := []network.TaggedMessageHandler{
{Tag: protocol.UniCatchupReqTag, MessageHandler: network.HandlerFunc(bs.processIncomingMessage)},
@@ -133,12 +141,14 @@ func (bs *BlockService) Start() {
// Stop servicing catchup requests over ws
func (bs *BlockService) Stop() {
+ bs.mu.Lock()
close(bs.stop)
+ bs.mu.Unlock()
bs.closeWaitGroup.Wait()
}
// ServerHTTP returns blocks
-// Either /v{version}/block/{round} or ?b={round}&v={version}
+// Either /v{version}/{genesisID}/block/{round} or ?b={round}&v={version}
// Uses gorilla/mux for path argument parsing.
func (bs *BlockService) ServeHTTP(response http.ResponseWriter, request *http.Request) {
pathVars := mux.Vars(request)
@@ -200,7 +210,7 @@ func (bs *BlockService) ServeHTTP(response http.ResponseWriter, request *http.Re
response.WriteHeader(http.StatusBadRequest)
return
}
- encodedBlockCert, err := RawBlockBytes(bs.ledger, basics.Round(round))
+ encodedBlockCert, err := bs.rawBlockBytes(basics.Round(round))
if err != nil {
switch err.(type) {
case ledgercore.ErrNoEntry:
@@ -321,7 +331,7 @@ func (bs *BlockService) redirectRequest(round uint64, response http.ResponseWrit
bs.log.Debugf("redirectRequest: %s", err.Error())
return false
}
- parsedURL.Path = FormatBlockQuery(round, parsedURL.Path, bs.net)
+ parsedURL.Path = strings.Replace(FormatBlockQuery(round, parsedURL.Path, bs.net), "{genesisID}", bs.genesisID, 1)
http.Redirect(response, request, parsedURL.String(), http.StatusTemporaryRedirect)
bs.log.Debugf("redirectRequest: redirected block request to %s", parsedURL.String())
return true
@@ -356,6 +366,22 @@ func (bs *BlockService) getRandomArchiver() (endpointAddress string) {
return
}
+// rawBlockBytes returns the block/cert for a given round, while taking the lock
+// to ensure the block service is currently active.
+func (bs *BlockService) rawBlockBytes(round basics.Round) ([]byte, error) {
+ bs.mu.Lock()
+ defer bs.mu.Unlock()
+ select {
+ case _, ok := <-bs.stop:
+ if !ok {
+ // service is closed.
+ return nil, errBlockServiceClosed
+ }
+ default:
+ }
+ return RawBlockBytes(bs.ledger, round)
+}
+
func topicBlockBytes(log logging.Logger, dataLedger *data.Ledger, round basics.Round, requestType string) network.Topics {
blk, cert, err := dataLedger.EncodedBlockCert(round)
if err != nil {
diff --git a/rpcs/blockService_test.go b/rpcs/blockService_test.go
index 542e8783ed..828f2265a9 100644
--- a/rpcs/blockService_test.go
+++ b/rpcs/blockService_test.go
@@ -19,7 +19,9 @@ package rpcs
import (
"context"
"fmt"
+ "io/ioutil"
"net/http"
+ "strings"
"testing"
"time"
@@ -118,7 +120,7 @@ func TestHandleCatchupReqNegative(t *testing.T) {
require.Equal(t, roundNumberParseErrMsg, string(val))
}
-// TestRedirectBasic tests the case when the block service redirects the request to elsewhere
+// TestRedirectFallbackArchiver tests the case when the block service fallback to another in the absense of a given block.
func TestRedirectFallbackArchiver(t *testing.T) {
partitiontest.PartitionTest(t)
@@ -136,8 +138,8 @@ func TestRedirectFallbackArchiver(t *testing.T) {
net2 := &httpTestPeerSource{}
config := config.GetDefaultLocal()
- bs1 := MakeBlockService(log, config, ledger1, net1, "{genesisID}")
- bs2 := MakeBlockService(log, config, ledger2, net2, "{genesisID}")
+ bs1 := MakeBlockService(log, config, ledger1, net1, "test-genesis-ID")
+ bs2 := MakeBlockService(log, config, ledger2, net2, "test-genesis-ID")
nodeA := &basicRPCNode{}
nodeB := &basicRPCNode{}
@@ -159,6 +161,7 @@ func TestRedirectFallbackArchiver(t *testing.T) {
ctx := context.Background()
parsedURL.Path = FormatBlockQuery(uint64(2), parsedURL.Path, net1)
+ parsedURL.Path = strings.Replace(parsedURL.Path, "{genesisID}", "test-genesis-ID", 1)
blockURL := parsedURL.String()
request, err := http.NewRequest("GET", blockURL, nil)
require.NoError(t, err)
@@ -170,6 +173,58 @@ func TestRedirectFallbackArchiver(t *testing.T) {
require.NoError(t, err)
require.Equal(t, http.StatusOK, response.StatusCode)
+ bodyData, err := ioutil.ReadAll(response.Body)
+ require.NoError(t, err)
+ require.NotEqual(t, 0, len(bodyData))
+}
+
+// TestBlockServiceShutdown tests that the block service is shutting down correctly.
+func TestBlockServiceShutdown(t *testing.T) {
+ partitiontest.PartitionTest(t)
+
+ log := logging.TestingLog(t)
+
+ ledger1 := makeLedger(t, "l1")
+ addBlock(t, ledger1)
+
+ net1 := &httpTestPeerSource{}
+
+ config := config.GetDefaultLocal()
+ bs1 := MakeBlockService(log, config, ledger1, net1, "test-genesis-ID")
+ bs1.Start()
+
+ nodeA := &basicRPCNode{}
+
+ nodeA.RegisterHTTPHandler(BlockServiceBlockPath, bs1)
+ nodeA.start()
+ defer nodeA.stop()
+
+ parsedURL, err := network.ParseHostOrURL(nodeA.rootURL())
+ require.NoError(t, err)
+
+ client := http.Client{}
+
+ ctx := context.Background()
+ parsedURL.Path = FormatBlockQuery(uint64(1), parsedURL.Path, net1)
+ parsedURL.Path = strings.Replace(parsedURL.Path, "{genesisID}", "test-genesis-ID", 1)
+ blockURL := parsedURL.String()
+ request, err := http.NewRequest("GET", blockURL, nil)
+ require.NoError(t, err)
+ requestCtx, requestCancel := context.WithTimeout(ctx, time.Duration(config.CatchupHTTPBlockFetchTimeoutSec)*time.Second)
+ defer requestCancel()
+ request = request.WithContext(requestCtx)
+ network.SetUserAgentHeader(request.Header)
+
+ requestDone := make(chan struct{})
+ go func() {
+ defer close(requestDone)
+ client.Do(request)
+ }()
+
+ bs1.Stop()
+ ledger1.Close()
+
+ <-requestDone
}
// TestRedirectBasic tests the case when the block service redirects the request to elsewhere
diff --git a/scripts/buildtools/go.mod b/scripts/buildtools/go.mod
index 18710c58b7..f3e0720fc7 100644
--- a/scripts/buildtools/go.mod
+++ b/scripts/buildtools/go.mod
@@ -1,6 +1,6 @@
module github.com/algorand/go-algorand/scripts/buildtools
-go 1.14
+go 1.16
require (
github.com/algorand/msgp v1.1.48
diff --git a/scripts/buildtools/install_buildtools.sh b/scripts/buildtools/install_buildtools.sh
index 630b99310e..edd346b383 100755
--- a/scripts/buildtools/install_buildtools.sh
+++ b/scripts/buildtools/install_buildtools.sh
@@ -70,16 +70,14 @@ function install_go_module {
# Check for version to go.mod version
VERSION=$(get_go_version "$1")
- # TODO: When we switch to 1.16 this should be changed to use 'go install'
- # instead of 'go get': https://tip.golang.org/doc/go1.16#modules
if [ -z "$VERSION" ]; then
echo "Unable to install requested package '$1' (${MODULE}): no version listed in ${SCRIPTPATH}/go.mod"
exit 1
else
- OUTPUT=$(GO111MODULE=on go get "${MODULE}@${VERSION}" 2>&1)
+ OUTPUT=$(go install "${MODULE}@${VERSION}" 2>&1)
fi
if [ $? != 0 ]; then
- echo "error: executing \"go get ${MODULE}\" failed : ${OUTPUT}"
+ echo "error: executing \"go install ${MODULE}\" failed : ${OUTPUT}"
exit 1
fi
}
diff --git a/scripts/create_and_deploy_recipe.sh b/scripts/create_and_deploy_recipe.sh
index 4c55edaf01..ea1f24edd2 100755
--- a/scripts/create_and_deploy_recipe.sh
+++ b/scripts/create_and_deploy_recipe.sh
@@ -2,7 +2,7 @@
# create_and_deploy_recipe.sh - Generates deployed network configuration (based on a recipe) and private build and pushes to S3
#
-# Syntax: create_and_deploy_recipe.sh -c [-n network] --recipe -r [--nodeploy] [--skip-build] [--force] [-m genesisVersionModifier] [ -b ]"
+# Syntax: create_and_deploy_recipe.sh -c [-n network] --recipe -r [--nodeploy] [--skip-build] [--force] [-m genesisVersionModifier] [ -b ] [--template ]"
#
# Outputs:
#
@@ -15,6 +15,9 @@
# Examples: create_and_deploy_recipe.sh -c TestCatchup --recipe test/testdata/deployednettemplates/recipes/devnet-like.config -r ~/networks/gen
#
# Notes: If you're running on a Mac, this will attempt to use docker to build for linux.
+# If you need to generate a recipe, you can pass in the --template flag with the path to the
+# template json and it will run the generate_recipe.py on the file. Make sure it's in the same
+# directory as the recipe file path.
set -e
@@ -80,6 +83,10 @@ while [ "$1" != "" ]; do
--skip-build)
SKIP_BUILD="true"
;;
+ --template)
+ shift
+ NETWORK_TEMPLATE="$1"
+ ;;
*)
echo "Unknown option" "$1"
exit 1
@@ -89,7 +96,7 @@ while [ "$1" != "" ]; do
done
if [[ -z "${CHANNEL}" || -z "${RECIPEFILE}" || -z "${ROOTDIR}" ]]; then
- echo "Syntax: create_and_deploy_recipe.sh -c [-n network] --recipe -r [--nodeploy] [--force]"
+ echo "Syntax: create_and_deploy_recipe.sh -c [-n network] --recipe -r [--nodeploy] [--force] [--template ]"
echo "e.g. create_and_deploy_recipe.sh -c TestCatchup --recipe test/testdata/deployednettemplates/recipes/devnet-like.config -r ~/networks//gen"
exit 1
fi
@@ -111,6 +118,11 @@ if [[ "${SKIP_BUILD}" != "true" || ! -f ${GOPATH}/bin/netgoal ]]; then
(cd ${SRCPATH} && make)
fi
+# If template is passed in, a recipe will be generated in the same folder as the template
+if [[ ! -z ${NETWORK_TEMPLATE} ]]; then
+ python3 ${SRCPATH}/test/testdata/deployednettemplates/generate-recipe/generate_network.py -f ${NETWORK_TEMPLATE}
+fi
+
# Generate the nodecfg package directory
${GOPATH}/bin/netgoal build -r "${ROOTDIR}" -n "${NETWORK}" --recipe "${RECIPEFILE}" "${FORCE_OPTION}" -m "${SCHEMA_MODIFIER}" -b=${BOOTSTRAP:-true}
diff --git a/scripts/get_golang_version.sh b/scripts/get_golang_version.sh
index a42b30709b..87f5372dfb 100755
--- a/scripts/get_golang_version.sh
+++ b/scripts/get_golang_version.sh
@@ -11,9 +11,9 @@
# Our build task-runner `mule` will refer to this script and will automatically
# build a new image whenever the version number has been changed.
-BUILD=1.14.7
-MIN=1.14
-GO_MOD_SUPPORT=1.12
+BUILD=1.16.11
+MIN=1.16
+GO_MOD_SUPPORT=1.16
if [ "$1" = all ]
then
diff --git a/scripts/install_linux_deps.sh b/scripts/install_linux_deps.sh
index 453bbaf709..78c42cd359 100755
--- a/scripts/install_linux_deps.sh
+++ b/scripts/install_linux_deps.sh
@@ -5,7 +5,7 @@ set -e
DISTRIB=$ID
ARCH_DEPS="boost boost-libs expect jq autoconf shellcheck sqlite python-virtualenv"
-UBUNTU_DEPS="libboost-all-dev expect jq autoconf shellcheck sqlite3 python3-venv"
+UBUNTU_DEPS="libtool libboost-math-dev expect jq autoconf shellcheck sqlite3 python3-venv"
FEDORA_DEPS="boost-devel expect jq autoconf ShellCheck sqlite python-virtualenv"
if [ "${DISTRIB}" = "arch" ]; then
diff --git a/scripts/travis/before_build.sh b/scripts/travis/before_build.sh
index b07efe33fa..3dbb7339d3 100755
--- a/scripts/travis/before_build.sh
+++ b/scripts/travis/before_build.sh
@@ -12,7 +12,6 @@ set -e
GOPATH=$(go env GOPATH)
export GOPATH
-export GO111MODULE=on
SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )"
OS=$("${SCRIPTPATH}"/../ostype.sh)
diff --git a/test/README.md b/test/README.md
index 2d89364606..e35a212510 100644
--- a/test/README.md
+++ b/test/README.md
@@ -46,14 +46,9 @@ optional arguments:
--version Future|vXX selects the network template file
```
-To run a specific test:
+To run a specific test, run e2e.sh with -i interactive flag, and follow the instructions:
```
-~$ ./e2e_client_runner.py /full/path/to/e2e_subs/test_script.sh
-```
-
-Make sure to install the Algorand Python SDK before running:
-```
-pip install py-algorand-sdk
+test/scripts/e2e.sh -i
```
Tests in the `e2e_subs/serial` directory are executed serially instead of in parallel. This should only be used when absolutely necessary.
diff --git a/test/e2e-go/cli/goal/expect/statefulTealCreateAppTest.exp b/test/e2e-go/cli/goal/expect/statefulTealCreateAppTest.exp
old mode 100644
new mode 100755
index d4118ad677..0051d65cc2
--- a/test/e2e-go/cli/goal/expect/statefulTealCreateAppTest.exp
+++ b/test/e2e-go/cli/goal/expect/statefulTealCreateAppTest.exp
@@ -121,7 +121,7 @@ proc statefulTealTest { TEST_ALGO_DIR TEST_DATA_DIR TEAL_PROGRAM} {
spawn goal app create --creator $PRIMARY_ACCOUNT_ADDRESS --approval-prog ${TEAL_PROGS_DIR}/${TEAL_PROGRAM} --global-byteslices $GLOBAL_BYTE_SLICES --global-ints 0 --local-byteslices $LOCAL_BYTE_SLICES --local-ints 0 --app-arg "str:hello" --clear-prog ${TEAL_PROGS_DIR}/${TEAL_PROGRAM} --extra-pages 5 -w $PRIMARY_WALLET_NAME -d $TEST_PRIMARY_NODE_DIR
expect {
timeout { puts timeout; ::AlgorandGoal::Abort "\n Failed to see expected output" }
- "tx.ExtraProgramPages too large, max number of extra pages is 3" {puts "received expected error"; close}
+ "tx.ExtraProgramPages exceeds MaxExtraAppProgramPages = 3" {puts "received expected error"; close}
eof { close; ::AlgorandGoal::Abort "did not receive expected error" }
}
diff --git a/test/e2e-go/features/devmode/devmode_test.go b/test/e2e-go/features/devmode/devmode_test.go
index 95801258dd..ae807a831c 100644
--- a/test/e2e-go/features/devmode/devmode_test.go
+++ b/test/e2e-go/features/devmode/devmode_test.go
@@ -32,17 +32,17 @@ import (
func TestDevMode(t *testing.T) {
partitiontest.PartitionTest(t)
+ t.Skipf("Skipping flaky test. Re-enable with #3267")
if testing.Short() {
t.Skip()
}
- t.Parallel()
-
// Start devmode network, and make sure everything is primed by sending a transaction.
var fixture fixtures.RestClientFixture
fixture.SetupNoStart(t, filepath.Join("nettemplates", "DevModeNetwork.json"))
fixture.Start()
+ defer fixture.Shutdown()
sender, err := fixture.GetRichestAccount()
require.NoError(t, err)
key := crypto.GenerateSignatureSecrets(crypto.Seed{})
@@ -56,7 +56,7 @@ func TestDevMode(t *testing.T) {
txn = fixture.SendMoneyAndWait(firstRound+i, 100000, 1000, sender.Address, receiver.String(), "")
require.Equal(t, firstRound+i, txn.FirstRound)
}
- require.True(t, time.Since(start) < 2*time.Second, "Transactions should be quickly confirmed.")
+ require.True(t, time.Since(start) < 8*time.Second, "Transactions should be quickly confirmed faster than usual.")
// Without transactions there should be no rounds even after a normal confirmation time.
time.Sleep(10 * time.Second)
diff --git a/test/e2e-go/features/participation/accountParticipationTransitions_test.go b/test/e2e-go/features/participation/accountParticipationTransitions_test.go
index 606ae7ce7a..5b2c3ea0c1 100644
--- a/test/e2e-go/features/participation/accountParticipationTransitions_test.go
+++ b/test/e2e-go/features/participation/accountParticipationTransitions_test.go
@@ -21,11 +21,14 @@ package participation
// deterministic.
import (
+ "fmt"
"io/ioutil"
"os"
"path/filepath"
"testing"
+ "time"
+ "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated"
@@ -70,17 +73,27 @@ func registerParticipationAndWait(t *testing.T, client libgoal.Client, part acco
func TestKeyRegistration(t *testing.T) {
partitiontest.PartitionTest(t)
+ t.Skipf("Skipping flaky test. Re-enable with #3255")
if testing.Short() {
t.Skip()
}
- t.Parallel()
+ checkKey := func(key generated.ParticipationKey, firstValid, lastValid, lastProposal uint64, msg string) {
+ require.NotNil(t, key.EffectiveFirstValid, fmt.Sprintf("%s.EffectiveFirstValid", msg))
+ require.NotNil(t, key.EffectiveLastValid, fmt.Sprintf("%s.EffectiveLastValid", msg))
+ require.NotNil(t, key.LastBlockProposal, fmt.Sprintf("%s.LastBlockProposal", msg))
+
+ assert.Equal(t, int(*(key.EffectiveFirstValid)), int(firstValid), fmt.Sprintf("%s.EffectiveFirstValid", msg))
+ assert.Equal(t, int(*(key.EffectiveLastValid)), int(lastValid), fmt.Sprintf("%s.EffectiveLastValid", msg))
+ assert.Equal(t, int(*(key.LastBlockProposal)), int(lastProposal), fmt.Sprintf("%s.LastBlockProposal", msg))
+ }
// Start devmode network and initialize things for the test.
var fixture fixtures.RestClientFixture
fixture.SetupNoStart(t, filepath.Join("nettemplates", "DevModeOneWallet.json"))
fixture.Start()
+ defer fixture.Shutdown()
sClient := fixture.GetLibGoalClientForNamedNode("Node")
minTxnFee, _, err := fixture.MinFeeAndBalance(0)
require.NoError(t, err)
@@ -92,7 +105,7 @@ func TestKeyRegistration(t *testing.T) {
last := uint64(6_000_000)
numNew := 2
for i := 0; i < numNew; i++ {
- response, part, err := installParticipationKey(t, sClient, sAccount, 0, last)
+ response, part, err := installParticipationKey(t, sClient, sAccount, 0, last+uint64(i))
require.NoError(t, err)
require.NotNil(t, response)
registerParticipationAndWait(t, sClient, part)
@@ -111,16 +124,41 @@ func TestKeyRegistration(t *testing.T) {
fixture.SendMoneyAndWait(2+i, 0, minTxnFee, sAccount, sAccount, "")
}
- keys, err = fixture.LibGoalClient.GetParticipationKeys()
- require.Equal(t, *(keys[0].EffectiveFirstValid), uint64(1))
- require.Equal(t, *(keys[0].EffectiveLastValid), lookback)
- require.Equal(t, *(keys[0].LastBlockProposal), lookback)
-
- require.Equal(t, *(keys[1].EffectiveFirstValid), lookback+1)
- require.Equal(t, *(keys[1].EffectiveLastValid), lookback+1)
- require.Equal(t, *(keys[1].LastBlockProposal), lookback+1)
+ // Wait until data has been persisted
+ ready := false
+ waitfor := time.After(1 * time.Minute)
+ for !ready {
+ select {
+ case <-waitfor:
+ ready = true
+ default:
+ keys, err = fixture.LibGoalClient.GetParticipationKeys()
+ ready = (len(keys) >= 3) &&
+ (keys[2].LastBlockProposal != nil) &&
+ (keys[2].EffectiveFirstValid != nil) &&
+ (keys[2].EffectiveLastValid != nil) &&
+ (keys[1].LastBlockProposal != nil) &&
+ (keys[1].EffectiveFirstValid != nil) &&
+ (keys[1].EffectiveLastValid != nil) &&
+ (keys[0].LastBlockProposal != nil) &&
+ (keys[0].EffectiveFirstValid != nil) &&
+ (keys[0].EffectiveLastValid != nil)
+ if !ready {
+ time.Sleep(100 * time.Millisecond)
+ }
+ }
+ }
- require.Equal(t, *(keys[2].EffectiveFirstValid), lookback+2)
- require.Equal(t, *(keys[2].EffectiveLastValid), last)
- require.Equal(t, *(keys[2].LastBlockProposal), lookback+2)
+ // Verify results, order may vary, key off of the last valid field
+ require.Len(t, keys, 3)
+ for _, k := range keys {
+ switch k.Key.VoteLastValid {
+ case 3_000_000:
+ checkKey(k, 1, lookback, lookback, "keys[0]")
+ case last:
+ checkKey(k, lookback+1, lookback+1, lookback+1, "keys[1]")
+ case last + 1:
+ checkKey(k, lookback+2, last+1, lookback+2, "keys[2]")
+ }
+ }
}
diff --git a/test/scripts/e2e.sh b/test/scripts/e2e.sh
index 9de5eaace2..0b4844654a 100755
--- a/test/scripts/e2e.sh
+++ b/test/scripts/e2e.sh
@@ -24,7 +24,7 @@ Options:
-n Run tests without building binaries (Binaries are expected in PATH)
"
NO_BUILD=false
-while getopts ":c:nh" opt; do
+while getopts ":c:nhi" opt; do
case ${opt} in
c ) CHANNEL=$OPTARG
;;
@@ -33,7 +33,11 @@ while getopts ":c:nh" opt; do
;;
h ) echo "${HELP}"
exit 0
- ;;
+ ;;
+ i ) echo " Interactive session"
+ echo "######################################################################"
+ INTERACTIVE=true
+ ;;
\? ) echo "${HELP}"
exit 2
;;
@@ -122,6 +126,23 @@ if [ -z "$E2E_TEST_FILTER" ] || [ "$E2E_TEST_FILTER" == "SCRIPTS" ]; then
"${TEMPDIR}/ve/bin/pip3" install --upgrade py-algorand-sdk cryptography
duration "e2e client setup"
+ if [ $INTERACTIVE ]; then
+ echo "********** READY **********"
+ echo "The test environment is now set. Run the tests using the following command on a different terminal after setting the path."
+ echo ""
+ echo "export VIRTUAL_ENV=\"${TEMPDIR}/ve\""
+ echo "export PATH=\"\$VIRTUAL_ENV/bin:\$PATH\""
+ echo ""
+ echo "${TEMPDIR}/ve/bin/python3" test/scripts/e2e_client_runner.py ${RUN_KMD_WITH_UNSAFE_SCRYPT} "$SRCROOT"/test/scripts/e2e_subs/SCRIPT_FILE_NAME
+ echo ""
+ echo "Press enter to shut down the test environment..."
+ read a
+ echo -n "deactivating..."
+ deactivate
+ echo "done"
+ exit
+ fi
+
"${TEMPDIR}/ve/bin/python3" e2e_client_runner.py ${RUN_KMD_WITH_UNSAFE_SCRYPT} "$SRCROOT"/test/scripts/e2e_subs/*.{sh,py}
duration "parallel client runner"
diff --git a/test/scripts/e2e_go_tests.sh b/test/scripts/e2e_go_tests.sh
index 1368f99e26..560808a7c3 100755
--- a/test/scripts/e2e_go_tests.sh
+++ b/test/scripts/e2e_go_tests.sh
@@ -101,7 +101,6 @@ cd ${SRCROOT}/test/e2e-go
# ARM64 has some memory related issues with fork. Since we don't really care
# about testing the forking capabilities, we're just run the tests one at a time.
-PARALLEL_FLAG=""
ARCHTYPE=$("${SRCROOT}/scripts/archtype.sh")
echo "ARCHTYPE: ${ARCHTYPE}"
if [[ "${ARCHTYPE}" = arm* ]]; then
diff --git a/test/scripts/e2e_subs/e2e-app-abi-add.sh b/test/scripts/e2e_subs/e2e-app-abi-add.sh
deleted file mode 100755
index 4ee0494b63..0000000000
--- a/test/scripts/e2e_subs/e2e-app-abi-add.sh
+++ /dev/null
@@ -1,61 +0,0 @@
-#!/bin/bash
-
-date '+app-abi-add-test start %Y%m%d_%H%M%S'
-
-set -e
-set -x
-set -o pipefail
-export SHELLOPTS
-
-WALLET=$1
-
-# Directory of this bash program
-DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
-
-gcmd="goal -w ${WALLET}"
-
-ACCOUNT=$(${gcmd} account list|awk '{ print $3 }')
-
-printf '#pragma version 2\nint 1' > "${TEMPDIR}/simple.teal"
-PROGRAM=($(${gcmd} clerk compile "${TEMPDIR}/simple.teal"))
-APPID=$(${gcmd} app create --creator ${ACCOUNT} --approval-prog ${DIR}/tealprogs/app-abi-add-example.teal --clear-prog ${TEMPDIR}/simple.teal --global-byteslices 0 --global-ints 0 --local-byteslices 1 --local-ints 0 | grep Created | awk '{ print $6 }')
-
-# Opt in
-RES=$(${gcmd} app method --method "optIn(string)string" --arg "\"Algorand Fan\"" --on-completion optin --app-id $APPID --from $ACCOUNT 2>&1 || true)
-EXPECTED="method optIn(string)string succeeded with output: \"hello Algorand Fan\""
-if [[ $RES != *"${EXPECTED}"* ]]; then
- date '+app-abi-add-test FAIL the method call to optIn(string)string should not fail %Y%m%d_%H%M%S'
- false
-fi
-
-# 1 + 2 = 3
-RES=$(${gcmd} app method --method "add(uint64,uint64)uint64" --arg 1 --arg 2 --app-id $APPID --from $ACCOUNT 2>&1 || true)
-EXPECTED="method add(uint64,uint64)uint64 succeeded with output: 3"
-if [[ $RES != *"${EXPECTED}"* ]]; then
- date '+app-abi-add-test FAIL the method call to add(uint64,uint64)uint64 should not fail %Y%m%d_%H%M%S'
- false
-fi
-
-# 18446744073709551614 + 1 = 18446744073709551615
-RES=$(${gcmd} app method --method "add(uint64,uint64)uint64" --arg 18446744073709551614 --arg 1 --app-id $APPID --from $ACCOUNT 2>&1 || true)
-EXPECTED="method add(uint64,uint64)uint64 succeeded with output: 18446744073709551615"
-if [[ $RES != *"${EXPECTED}"* ]]; then
- date '+app-abi-add-test FAIL the method call to add(uint64,uint64)uint64 should not fail %Y%m%d_%H%M%S'
- false
-fi
-
-# Close out
-RES=$(${gcmd} app method --method "closeOut()string" --on-completion closeout --app-id $APPID --from $ACCOUNT 2>&1 || true)
-EXPECTED="method closeOut()string succeeded with output: \"goodbye Algorand Fan\""
-if [[ $RES != *"${EXPECTED}"* ]]; then
- date '+app-abi-add-test FAIL the method call to closeOut()string should not fail %Y%m%d_%H%M%S'
- false
-fi
-
-# Delete
-RES=$(${gcmd} app method --method "delete()void" --on-completion deleteapplication --app-id $APPID --from $ACCOUNT 2>&1 || true)
-EXPECTED="method delete()void succeeded"
-if [[ $RES != *"${EXPECTED}"* ]]; then
- date '+app-abi-add-test FAIL the method call to delete()void should not fail %Y%m%d_%H%M%S'
- false
-fi
diff --git a/test/scripts/e2e_subs/e2e-app-abi-method.sh b/test/scripts/e2e_subs/e2e-app-abi-method.sh
new file mode 100755
index 0000000000..72bf746ddd
--- /dev/null
+++ b/test/scripts/e2e_subs/e2e-app-abi-method.sh
@@ -0,0 +1,87 @@
+#!/bin/bash
+
+date '+app-abi-method-test start %Y%m%d_%H%M%S'
+
+set -e
+set -x
+set -o pipefail
+export SHELLOPTS
+
+WALLET=$1
+
+# Directory of this bash program
+DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
+
+gcmd="goal -w ${WALLET}"
+
+ACCOUNT=$(${gcmd} account list|awk '{ print $3 }')
+
+printf '#pragma version 2\nint 1' > "${TEMPDIR}/simple.teal"
+PROGRAM=($(${gcmd} clerk compile "${TEMPDIR}/simple.teal"))
+APPID=$(${gcmd} app create --creator ${ACCOUNT} --approval-prog ${DIR}/tealprogs/app-abi-method-example.teal --clear-prog ${TEMPDIR}/simple.teal --global-byteslices 0 --global-ints 0 --local-byteslices 1 --local-ints 0 | grep Created | awk '{ print $6 }')
+
+# Opt in
+RES=$(${gcmd} app method --method "optIn(string)string" --arg "\"Algorand Fan\"" --on-completion optin --app-id $APPID --from $ACCOUNT 2>&1 || true)
+EXPECTED="method optIn(string)string succeeded with output: \"hello Algorand Fan\""
+if [[ $RES != *"${EXPECTED}"* ]]; then
+ date '+app-abi-method-test FAIL the method call to optIn(string)string should not fail %Y%m%d_%H%M%S'
+ false
+fi
+
+# 1 + 2 = 3
+RES=$(${gcmd} app method --method "add(uint64,uint64)uint64" --arg 1 --arg 2 --app-id $APPID --from $ACCOUNT 2>&1 || true)
+EXPECTED="method add(uint64,uint64)uint64 succeeded with output: 3"
+if [[ $RES != *"${EXPECTED}"* ]]; then
+ date '+app-abi-method-test FAIL the method call to add(uint64,uint64)uint64 should not fail %Y%m%d_%H%M%S'
+ false
+fi
+
+# 18446744073709551614 + 1 = 18446744073709551615
+RES=$(${gcmd} app method --method "add(uint64,uint64)uint64" --arg 18446744073709551614 --arg 1 --app-id $APPID --from $ACCOUNT 2>&1 || true)
+EXPECTED="method add(uint64,uint64)uint64 succeeded with output: 18446744073709551615"
+if [[ $RES != *"${EXPECTED}"* ]]; then
+ date '+app-abi-method-test FAIL the method call to add(uint64,uint64)uint64 should not fail %Y%m%d_%H%M%S'
+ false
+fi
+
+goal clerk send --from $ACCOUNT --to $ACCOUNT --amount 1000000 -o "${TEMPDIR}/pay-txn-arg.tx"
+
+# Payment with return true
+RES=$(${gcmd} app method --method "payment(pay,uint64)bool" --arg ${TEMPDIR}/pay-txn-arg.tx --arg 1000000 --app-id $APPID --from $ACCOUNT 2>&1 || true)
+EXPECTED="method payment(pay,uint64)bool succeeded with output: true"
+if [[ $RES != *"${EXPECTED}"* ]]; then
+ date '+app-abi-method-test FAIL the method call to payment(pay,uint64)bool should not fail %Y%m%d_%H%M%S'
+ false
+fi
+
+# Payment with return false
+RES=$(${gcmd} app method --method "payment(pay,uint64)bool" --arg ${TEMPDIR}/pay-txn-arg.tx --arg 1000001 --app-id $APPID --from $ACCOUNT 2>&1 || true)
+EXPECTED="method payment(pay,uint64)bool succeeded with output: false"
+if [[ $RES != *"${EXPECTED}"* ]]; then
+ date '+app-abi-method-test FAIL the method call to payment(pay,uint64)bool should not fail %Y%m%d_%H%M%S'
+ false
+fi
+
+# Foreign reference test
+RES=$(${gcmd} app method --method "referenceTest(account,application,account,asset,account,asset,asset,application,application)uint8[9]" --arg KGTOR3F3Q74JP4LB5M3SOCSJ4BOPOKZ2GPSLMLLGCWYWRXZJNN4LYQJXXU --arg $APPID --arg $ACCOUNT --arg 10 --arg KGTOR3F3Q74JP4LB5M3SOCSJ4BOPOKZ2GPSLMLLGCWYWRXZJNN4LYQJXXU --arg 11 --arg 10 --arg 20 --arg 21 --app-account 2R5LMPTYLVMWYEG4RPI26PJAM7ARTGUB7LZSONQPGLUWTPOP6LQCJTQZVE --foreign-app 21 --foreign-asset 10 --app-id $APPID --from $ACCOUNT 2>&1 || true)
+EXPECTED="method referenceTest(account,application,account,asset,account,asset,asset,application,application)uint8[9] succeeded with output: [2,0,2,0,2,1,0,1,0]"
+if [[ $RES != *"${EXPECTED}"* ]]; then
+ date '+app-abi-method-test FAIL the method call to referenceTest(account,application,account,asset,account,asset,asset,application,application)uint8[9] should not fail %Y%m%d_%H%M%S'
+ false
+fi
+
+# Close out
+RES=$(${gcmd} app method --method "closeOut()string" --on-completion closeout --app-id $APPID --from $ACCOUNT 2>&1 || true)
+EXPECTED="method closeOut()string succeeded with output: \"goodbye Algorand Fan\""
+if [[ $RES != *"${EXPECTED}"* ]]; then
+ date '+app-abi-method-test FAIL the method call to closeOut()string should not fail %Y%m%d_%H%M%S'
+ false
+fi
+
+# Delete
+RES=$(${gcmd} app method --method "delete()void" --on-completion deleteapplication --app-id $APPID --from $ACCOUNT 2>&1 || true)
+EXPECTED="method delete()void succeeded"
+if [[ $RES != *"${EXPECTED}"* ]]; then
+ date '+app-abi-method-test FAIL the method call to delete()void should not fail %Y%m%d_%H%M%S'
+ false
+fi
diff --git a/test/scripts/e2e_subs/rest-participation-key.sh b/test/scripts/e2e_subs/rest-participation-key.sh
index 30557ff2a9..e5f7a30b36 100755
--- a/test/scripts/e2e_subs/rest-participation-key.sh
+++ b/test/scripts/e2e_subs/rest-participation-key.sh
@@ -9,38 +9,28 @@ date "+$0 start %Y%m%d_%H%M%S"
# Use admin token for both get and post
export USE_ADMIN=true
-pushd "${TEMPDIR}" || exit
+pushd "${TEMPDIR}" || exit 1
FIRST_ROUND=0
# A really large (but arbitrary) last valid round
-LAST_ROUND=1200000
+LAST_ROUND=120
NAME_OF_TEMP_PARTKEY="tmp.${FIRST_ROUND}.${LAST_ROUND}.partkey"
algokey part generate --first ${FIRST_ROUND} --last ${LAST_ROUND} --keyfile ${NAME_OF_TEMP_PARTKEY} --parent ${ACCOUNT}
-popd || exit
+popd || exit 1
-call_and_verify "Get List of Keys" "/v2/participation" 200 'address'
-
-# Find out how many keys there are installed so far
-NUM_IDS_1=$(echo "$RES" | python3 -c 'import json,sys;o=json.load(sys.stdin);print(len(o))')
+call_and_verify "Get List of Keys" "/v2/participation" 200 'address' 'effective-first-valid'
+RES=""
call_post_and_verify "Install a basic participation key" "/v2/participation" 200 ${NAME_OF_TEMP_PARTKEY} 'partId'
# Get the returned participation id from the RESULT (aka $RES) variable
INSTALLED_ID=$(echo "$RES" | python3 -c 'import json,sys;o=json.load(sys.stdin);print(o["partId"])')
# Should contain the installed id
-call_and_verify "Get List of Keys" "/v2/participation" 200 'address' "${INSTALLED_ID}"
-
-# Get list of keys
-NUM_IDS_2=$(echo "$RES" | python3 -c 'import json,sys;o=json.load(sys.stdin);print(len(o))')
-
-if [[ $((NUM_IDS_1 + 1)) -ne $NUM_IDS_2 ]]; then
- printf "\n\nFailed test. New number of IDs (%s) is not one more than old ID count(%s)\n\n" "${NUM_IDS_2}" "${NUM_IDS_1}"
- exit 1
-fi
+call_and_verify "Get List of Keys" "/v2/participation" 200 'address' "${INSTALLED_ID}" 'address' 'effective-first-valid'
call_and_verify "Get a specific ID" "/v2/participation/${INSTALLED_ID}" 200 "${INSTALLED_ID}"
@@ -49,13 +39,3 @@ call_delete_and_verify "Delete the specific ID" "/v2/participation/${INSTALLED_I
# Verify that it got called previously and now returns an error message saying that no key was found
call_delete_and_verify "Delete the specific ID" "/v2/participation/${INSTALLED_ID}" 404 true 'participation id not found'
-
-# Get list of keys
-NUM_IDS_3=$(echo "$RES" | python3 -c 'import json,sys;o=json.load(sys.stdin);print(len(o))')
-
-if [[ "$NUM_IDS_3" -ne "$NUM_IDS_1" ]]; then
- printf "\n\nFailed test. New number of IDs (%s) is not equal to original ID count (%s)\n\n" "${NUM_IDS_3}" "${NUM_IDS_1}"
- exit 1
-fi
-
-
diff --git a/test/scripts/e2e_subs/tealprogs/app-abi-add-example.teal b/test/scripts/e2e_subs/tealprogs/app-abi-method-example.teal
similarity index 69%
rename from test/scripts/e2e_subs/tealprogs/app-abi-add-example.teal
rename to test/scripts/e2e_subs/tealprogs/app-abi-method-example.teal
index a2c8168765..2711cd333f 100644
--- a/test/scripts/e2e_subs/tealprogs/app-abi-add-example.teal
+++ b/test/scripts/e2e_subs/tealprogs/app-abi-method-example.teal
@@ -3,7 +3,7 @@
txn ApplicationID
int 0
==
-bnz main_l14
+bnz main_l16
txn OnCompletion
int OptIn
==
@@ -11,7 +11,7 @@ txna ApplicationArgs 0
byte 0xcfa68e36
==
&&
-bnz main_l13
+bnz main_l15
txn OnCompletion
int CloseOut
==
@@ -19,7 +19,7 @@ txna ApplicationArgs 0
byte 0xa9f42b3d
==
&&
-bnz main_l12
+bnz main_l14
txn OnCompletion
int DeleteApplication
==
@@ -27,7 +27,7 @@ txna ApplicationArgs 0
byte 0x24378d3c
==
&&
-bnz main_l11
+bnz main_l13
txn OnCompletion
int NoOp
==
@@ -35,7 +35,7 @@ txna ApplicationArgs 0
byte 0xfe6bdf69
==
&&
-bnz main_l10
+bnz main_l12
txn OnCompletion
int NoOp
==
@@ -43,46 +43,67 @@ txna ApplicationArgs 0
byte 0xa88c26a5
==
&&
-bnz main_l9
+bnz main_l11
+txn OnCompletion
+int NoOp
+==
+txna ApplicationArgs 0
+byte 0x3e3b3d28
+==
+&&
+bnz main_l10
txn OnCompletion
int NoOp
==
txna ApplicationArgs 0
-byte 0x535a47ba
+byte 0x0df0050f
==
&&
-bnz main_l8
+bnz main_l9
int 0
return
-main_l8:
+main_l9:
+txna ApplicationArgs 1
+txna ApplicationArgs 2
+txna ApplicationArgs 3
+txna ApplicationArgs 4
+txna ApplicationArgs 5
+txna ApplicationArgs 6
+txna ApplicationArgs 7
+txna ApplicationArgs 8
+txna ApplicationArgs 9
+callsub sub6
+int 1
+return
+main_l10:
txna ApplicationArgs 1
callsub sub5
int 1
return
-main_l9:
+main_l11:
callsub sub4
int 1
return
-main_l10:
+main_l12:
txna ApplicationArgs 1
txna ApplicationArgs 2
callsub sub3
int 1
return
-main_l11:
+main_l13:
callsub sub2
int 1
return
-main_l12:
+main_l14:
callsub sub1
int 1
return
-main_l13:
+main_l15:
txna ApplicationArgs 1
callsub sub0
int 1
return
-main_l14:
+main_l16:
int 1
return
sub0: // optIn
@@ -158,7 +179,50 @@ int pay
==
assert
byte 0x151f7c75
+txn GroupIndex
+int 1
+-
+gtxns Amount
+load 5
+btoi
+==
+bnz sub5_l2
+byte 0x00
+b sub5_l3
+sub5_l2:
byte 0x80
+sub5_l3:
concat
log
-retsub
\ No newline at end of file
+retsub
+sub6: // referenceTest
+store 14
+store 13
+store 12
+store 11
+store 10
+store 9
+store 8
+store 7
+store 6
+byte 0x151f7c75
+load 6
+concat
+load 8
+concat
+load 10
+concat
+load 7
+concat
+load 13
+concat
+load 14
+concat
+load 9
+concat
+load 11
+concat
+load 12
+concat
+log
+retsub
diff --git a/test/testdata/deployednettemplates/generate-recipe/generate_network.py b/test/testdata/deployednettemplates/generate-recipe/generate_network.py
index 0804f7d96c..aeeef43841 100755
--- a/test/testdata/deployednettemplates/generate-recipe/generate_network.py
+++ b/test/testdata/deployednettemplates/generate-recipe/generate_network.py
@@ -25,7 +25,7 @@ def build_network(template):
netgoal_params = build_netgoal_params(template_dict)
build_net(template_path, netgoal_params)
- build_genesis(template_path, netgoal_params)
+ build_genesis(template_path, netgoal_params, template_dict)
def build_netgoal_params(template_dict):
instances = template_dict['instances']
@@ -38,7 +38,6 @@ def build_netgoal_params(template_dict):
relay_count += getInstanceCount(instances['relays'], group['percent']['relays'])
participating_node_count += getInstanceCount(instances['participatingNodes'], group['percent']['participatingNodes'])
non_participating_node_count += getInstanceCount(instances['nonParticipatingNodes'], group['percent']['nonParticipatingNodes'])
-
relay_config = instances['relays']['config']
participating_node_config = instances['participatingNodes']['config']
@@ -66,13 +65,22 @@ def build_net(template_path, netgoal_params):
args.extend(netgoal_params)
netgoal(args, template_path)
-def build_genesis(template_path, netgoal_params):
+def build_genesis(template_path, netgoal_params, template_dict):
args = [
'-t', 'genesis',
'-o', f"{template_path}/generated/genesis.json"
]
args.extend(netgoal_params)
netgoal(args, template_path)
+ if template_dict['network']['ConsensusProtocol']:
+ updateProtocol(f"{template_path}/generated/genesis.json", template_dict['network']['ConsensusProtocol'])
+
+def updateProtocol(genesis_path, consensus_protocol):
+ with open(genesis_path, 'r') as genfile:
+ genjson = json.load(genfile)
+ genjson["ConsensusProtocol"] = consensus_protocol
+ with open(genesis_path, 'w') as genfile:
+ json.dump(genjson, genfile, indent="\t")
def netgoal(args, template_path='.'):
cmd = [
diff --git a/test/testdata/deployednettemplates/recipes/custom/README.md b/test/testdata/deployednettemplates/recipes/custom/README.md
new file mode 100644
index 0000000000..78c0d3330e
--- /dev/null
+++ b/test/testdata/deployednettemplates/recipes/custom/README.md
@@ -0,0 +1,84 @@
+# Custom Recipe
+This custom recipe serves as a template for performance testing on algonet (new network on AWS EC2 machines). With this recipe, you can modify the number of nodes, the type of machines, introduce new parameters to modify the network's configs and consensus parameters.
+
+N = participating Nodes
+NPN = Non-Participating Nodes
+R = relays
+
+## Running a Small Network (less than 20 total nodes)
+If you are running a network with less than 20 nodes, then you will need to update the default "FractionApply"
+1. Modify `configs/node.json` folder
+ - `"FractionApply"` in configs/node.json represents the number of nodes to report to telemetry. We don't want to overwhelm the telemetry server, so use something small like "0.2" on a large network.
+ - For small networks, update this value to "1.0"
+
+## Quick Start - Jenkins
+Build and create the recipe.
+- (See the first section above for small networks.)
+1. Modify the `network_templates/network-tpl.json` file.
+2. Select "custom" recipe
+3. Specify `network-tpl.json` as the `CUSTOM_NETWORK_TEMPLATE`
+- See Modify consensus values (below) to update consensus
+- See Update config.json (below) to update config.json
+
+## "Quick" Start - Manual recipe generation (not using Jenkins)
+Generate the recipe with the `network-tpl.json` file
+- (See the first section above for small networks.)
+1. Make sure you're in the same directory as this README and `cp network_templates/network-tpl.json network-tpl.json`
+2. Generate the recipe with a python script:
+```
+cd go-algorand
+python3 test/testdata/deployednettemplates/generate-recipe/generate_network.py -f test/testdata/deployednettemplates/recipes/custom/network-tpl.json
+```
+3. This will create a new set of files in the `generated` folder
+
+## Network Templates
+With the custom recipe, you can store multiple network templates in the network_templates directory.
+Variables to modify:
+- `wallets`: Number of wallets used by N
+- `nodes`: Number of N
+- `ConsensusProtocol`: ConsensusProtocol used for the genesis
+- `type`: machine sizes. For `us-east-2`, you can use `m5d.4xl` for most testing. If you need more powerful compute (make sure you get approval because it can get costly) use `c4d.4xl` or `c4d.18xl`
+- `count`: Number of machines per type
+- `percent`: percentage of machines in group to dedicate to certain types of nodes.
+
+## Modify consensus values
+If you add a `consensus.json` file in this folder with the protocol matching the one in `network-tpl.json`, the `consensus.json` will merge with a generated_consensus.json template on Jenkins.
+- see `example/consensus.json`
+
+### How is consensus updated in Jenkins?
+- In Jenkins, this will be generated via `goal protocols > generated_consensus.json`
+- This means that you do not have to provide the whole `consensus.json` in this folder, but only the values you wish to update.
+- If you are spinning up a network manually and wish to update a network with `consensus.json`, you must have all of the existing keys for the particular protocol in your consensus.json.
+
+## Update config.json in the network
+If you look at the files in the "configs" folder, you will see `node.json`, `nonPartNode.json`, and `relay.json`. These jsons already have a `ConfigJSONOverride` parameter which will generate a config.json in the node's data directories. For testing, if you want to update all three types of nodes at once, you can save a `config.json` file here.
+1. copy and paste something like this into a json file and save into `config_jsons`:
+```
+{
+ "ProposalAssemblyTime": 250000000,
+ "TxPoolSize": 20000
+}
+```
+This file will merge with the config.json created by `ConfigJSONOverride` and update the parameters if the keys match. This will be applied to participating nodes, nonParticipating Nodes, and relays.
+
+See `example/config_jsons` for an example of what it should look like.
+
+Most parameters that can be modified by config.json can be found in `go-algorand/config/local_defaults.go`.
+
+## Troubleshooting
+### Can't find netgoal
+- Make sure you have netgoal installed
+- Make sure you export GOBIN and GOPATH in your environment and add it to your path.
+On a mac, update by editing `~/.zshrc`, add
+```
+export GOBIN=/Users/{user}/go/bin
+
+export GOPATH=/Users/{user}/go
+export PATH=$PATH:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/Users/ec2-user/Library/Python/3.8/bin:/usr/local/go/bin:$GOBIN:$GOPATH
+
+```
+### Machine Type doesn't exist
+- Make sure the machine type exists. It uses the regions in the groups and the type to come up with the host template name in `test/testdata/deployednettemplates/hosttemplates/hosttemplates.json`. If it doesn't exist, you will have to add it to that file.
+
+### couldn't initialize the node: unsupported protocol
+- check your consensus.json. It may be missing the keys in the future protocol if you are doing this manually. Compare the consensus.json with `goal protocols > generated_consensus.json`
\ No newline at end of file
diff --git a/test/testdata/deployednettemplates/recipes/custom/configs/node.json b/test/testdata/deployednettemplates/recipes/custom/configs/node.json
new file mode 100644
index 0000000000..a61c7506d5
--- /dev/null
+++ b/test/testdata/deployednettemplates/recipes/custom/configs/node.json
@@ -0,0 +1,22 @@
+{
+ "APIToken": "{{APIToken}}",
+ "EnableBlockStats": false,
+ "EnableTelemetry": false,
+ "TelemetryURI": "{{TelemetryURI}}",
+ "EnableMetrics": false,
+ "MetricsURI": "{{MetricsURI}}",
+ "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"CadaverSizeTarget\": 0 }",
+ "AltConfigs": [
+ {
+ "APIToken": "{{APIToken}}",
+ "EnableBlockStats": true,
+ "EnableTelemetry": true,
+ "TelemetryURI": "{{TelemetryURI}}",
+ "EnableMetrics": true,
+ "MetricsURI": "{{MetricsURI}}",
+ "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"CadaverSizeTarget\": 0 }",
+ "FractionApply": 1.0
+ }
+ ]
+}
+
diff --git a/test/testdata/deployednettemplates/recipes/custom/configs/nonPartNode.json b/test/testdata/deployednettemplates/recipes/custom/configs/nonPartNode.json
new file mode 100644
index 0000000000..5b0a52d9d9
--- /dev/null
+++ b/test/testdata/deployednettemplates/recipes/custom/configs/nonPartNode.json
@@ -0,0 +1,5 @@
+{
+ "APIEndpoint": "{{APIEndpoint}}",
+ "APIToken": "{{APIToken}}",
+ "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"BaseLoggerDebugLevel\": 4, \"CadaverSizeTarget\": 0 }"
+}
diff --git a/test/testdata/deployednettemplates/recipes/custom/configs/relay.json b/test/testdata/deployednettemplates/recipes/custom/configs/relay.json
new file mode 100644
index 0000000000..25bb6b5a26
--- /dev/null
+++ b/test/testdata/deployednettemplates/recipes/custom/configs/relay.json
@@ -0,0 +1,11 @@
+{
+ "NetAddress": "{{NetworkPort}}",
+ "APIEndpoint": "{{APIEndpoint}}",
+ "APIToken": "{{APIToken}}",
+ "EnableBlockStats": true,
+ "EnableTelemetry": true,
+ "TelemetryURI": "{{TelemetryURI}}",
+ "EnableMetrics": true,
+ "MetricsURI": "{{MetricsURI}}",
+ "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"EnableIncomingMessageFilter\": true, \"CadaverSizeTarget\": 0, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true }"
+}
diff --git a/test/testdata/deployednettemplates/recipes/custom/example/README.md b/test/testdata/deployednettemplates/recipes/custom/example/README.md
new file mode 100644
index 0000000000..3af532533e
--- /dev/null
+++ b/test/testdata/deployednettemplates/recipes/custom/example/README.md
@@ -0,0 +1,12 @@
+# Example for custom recipe
+Feel free to copy over and replace custom recipe with the contents in this folder or read the descriptions below.
+
+## config_jsons
+example of config.json that you can include. The `custom/config_jsons` folder has a README with more information.
+
+## netowrk_templates
+Shows that you can have more than one network template in the custom recipe. You can specify the template in the Jenkins Pipeline.
+
+## consensus.json
+An example of the consensus.json that you can copy into the custom folder to update the "future" protocol.
+
diff --git a/test/testdata/deployednettemplates/recipes/custom/example/config_jsons/config.json b/test/testdata/deployednettemplates/recipes/custom/example/config_jsons/config.json
new file mode 100644
index 0000000000..1fe1bd252c
--- /dev/null
+++ b/test/testdata/deployednettemplates/recipes/custom/example/config_jsons/config.json
@@ -0,0 +1,4 @@
+{
+ "ProposalAssemblyTime": 250000000,
+ "TxPoolSize": 20000
+}
\ No newline at end of file
diff --git a/test/testdata/deployednettemplates/recipes/custom/example/configs/node.json b/test/testdata/deployednettemplates/recipes/custom/example/configs/node.json
new file mode 100644
index 0000000000..a61c7506d5
--- /dev/null
+++ b/test/testdata/deployednettemplates/recipes/custom/example/configs/node.json
@@ -0,0 +1,22 @@
+{
+ "APIToken": "{{APIToken}}",
+ "EnableBlockStats": false,
+ "EnableTelemetry": false,
+ "TelemetryURI": "{{TelemetryURI}}",
+ "EnableMetrics": false,
+ "MetricsURI": "{{MetricsURI}}",
+ "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"CadaverSizeTarget\": 0 }",
+ "AltConfigs": [
+ {
+ "APIToken": "{{APIToken}}",
+ "EnableBlockStats": true,
+ "EnableTelemetry": true,
+ "TelemetryURI": "{{TelemetryURI}}",
+ "EnableMetrics": true,
+ "MetricsURI": "{{MetricsURI}}",
+ "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true, \"CadaverSizeTarget\": 0 }",
+ "FractionApply": 1.0
+ }
+ ]
+}
+
diff --git a/test/testdata/deployednettemplates/recipes/custom/example/configs/nonPartNode.json b/test/testdata/deployednettemplates/recipes/custom/example/configs/nonPartNode.json
new file mode 100644
index 0000000000..5b0a52d9d9
--- /dev/null
+++ b/test/testdata/deployednettemplates/recipes/custom/example/configs/nonPartNode.json
@@ -0,0 +1,5 @@
+{
+ "APIEndpoint": "{{APIEndpoint}}",
+ "APIToken": "{{APIToken}}",
+ "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"BaseLoggerDebugLevel\": 4, \"CadaverSizeTarget\": 0 }"
+}
diff --git a/test/testdata/deployednettemplates/recipes/custom/example/configs/relay.json b/test/testdata/deployednettemplates/recipes/custom/example/configs/relay.json
new file mode 100644
index 0000000000..25bb6b5a26
--- /dev/null
+++ b/test/testdata/deployednettemplates/recipes/custom/example/configs/relay.json
@@ -0,0 +1,11 @@
+{
+ "NetAddress": "{{NetworkPort}}",
+ "APIEndpoint": "{{APIEndpoint}}",
+ "APIToken": "{{APIToken}}",
+ "EnableBlockStats": true,
+ "EnableTelemetry": true,
+ "TelemetryURI": "{{TelemetryURI}}",
+ "EnableMetrics": true,
+ "MetricsURI": "{{MetricsURI}}",
+ "ConfigJSONOverride": "{ \"TxPoolExponentialIncreaseFactor\": 1, \"DNSBootstrapID\": \".algodev.network\", \"DeadlockDetection\": -1, \"EnableIncomingMessageFilter\": true, \"CadaverSizeTarget\": 0, \"PeerPingPeriodSeconds\": 30, \"EnableAgreementReporting\": true, \"EnableAgreementTimeMetrics\": true, \"EnableAssembleStats\": true, \"EnableProcessBlockStats\": true, \"BaseLoggerDebugLevel\": 4, \"EnableProfiler\": true }"
+}
diff --git a/test/testdata/deployednettemplates/recipes/custom/example/consensus.json b/test/testdata/deployednettemplates/recipes/custom/example/consensus.json
new file mode 100644
index 0000000000..71512af006
--- /dev/null
+++ b/test/testdata/deployednettemplates/recipes/custom/example/consensus.json
@@ -0,0 +1,6 @@
+{
+ "future": {
+ "AgreementFilterTimeoutPeriod0": 4000000001,
+ "MaxTxnBytesPerBlock": 1100001
+ }
+}
\ No newline at end of file
diff --git a/test/testdata/deployednettemplates/recipes/custom/example/network_templates/c5dmachines.json b/test/testdata/deployednettemplates/recipes/custom/example/network_templates/c5dmachines.json
new file mode 100644
index 0000000000..1f6a8b2fba
--- /dev/null
+++ b/test/testdata/deployednettemplates/recipes/custom/example/network_templates/c5dmachines.json
@@ -0,0 +1,44 @@
+{
+ "network": {
+ "wallets": 6,
+ "nodes": 3,
+ "ConsensusProtocol": "future"
+ },
+ "instances": {
+ "relays": {
+ "config": "./configs/relay.json",
+ "type": "c5d.4xl",
+ "count": 1
+ },
+ "participatingNodes": {
+ "config": "./configs/node.json",
+ "type": "c5d.4xl",
+ "count": 3
+ },
+ "nonParticipatingNodes": {
+ "config": "./configs/nonPartNode.json",
+ "type": "c5d.4xl",
+ "count": 5
+ }
+ },
+ "groups": [
+ {
+ "name": "us-r",
+ "percent": {
+ "relays": 100,
+ "participatingNodes": 0,
+ "nonParticipatingNodes": 0
+ },
+ "region": "us-east-2"
+ },
+ {
+ "name": "us-n",
+ "percent": {
+ "relays": 0,
+ "participatingNodes": 100,
+ "nonParticipatingNodes": 100
+ },
+ "region": "us-east-2"
+ }
+ ]
+}
diff --git a/test/testdata/deployednettemplates/recipes/custom/example/network_templates/network-tpl.json b/test/testdata/deployednettemplates/recipes/custom/example/network_templates/network-tpl.json
new file mode 100644
index 0000000000..5bc36419d1
--- /dev/null
+++ b/test/testdata/deployednettemplates/recipes/custom/example/network_templates/network-tpl.json
@@ -0,0 +1,44 @@
+{
+ "network": {
+ "wallets": 6,
+ "nodes": 3,
+ "ConsensusProtocol": "future"
+ },
+ "instances": {
+ "relays": {
+ "config": "./configs/relay.json",
+ "type": "m5d.4xl",
+ "count": 1
+ },
+ "participatingNodes": {
+ "config": "./configs/node.json",
+ "type": "m5d.4xl",
+ "count": 3
+ },
+ "nonParticipatingNodes": {
+ "config": "./configs/nonPartNode.json",
+ "type": "m5d.4xl",
+ "count": 5
+ }
+ },
+ "groups": [
+ {
+ "name": "us-r",
+ "percent": {
+ "relays": 100,
+ "participatingNodes": 0,
+ "nonParticipatingNodes": 0
+ },
+ "region": "us-east-2"
+ },
+ {
+ "name": "us-n",
+ "percent": {
+ "relays": 0,
+ "participatingNodes": 100,
+ "nonParticipatingNodes": 100
+ },
+ "region": "us-east-2"
+ }
+ ]
+}
diff --git a/test/testdata/deployednettemplates/recipes/custom/network_templates/network-tpl.json b/test/testdata/deployednettemplates/recipes/custom/network_templates/network-tpl.json
new file mode 100644
index 0000000000..5bc36419d1
--- /dev/null
+++ b/test/testdata/deployednettemplates/recipes/custom/network_templates/network-tpl.json
@@ -0,0 +1,44 @@
+{
+ "network": {
+ "wallets": 6,
+ "nodes": 3,
+ "ConsensusProtocol": "future"
+ },
+ "instances": {
+ "relays": {
+ "config": "./configs/relay.json",
+ "type": "m5d.4xl",
+ "count": 1
+ },
+ "participatingNodes": {
+ "config": "./configs/node.json",
+ "type": "m5d.4xl",
+ "count": 3
+ },
+ "nonParticipatingNodes": {
+ "config": "./configs/nonPartNode.json",
+ "type": "m5d.4xl",
+ "count": 5
+ }
+ },
+ "groups": [
+ {
+ "name": "us-r",
+ "percent": {
+ "relays": 100,
+ "participatingNodes": 0,
+ "nonParticipatingNodes": 0
+ },
+ "region": "us-east-2"
+ },
+ {
+ "name": "us-n",
+ "percent": {
+ "relays": 0,
+ "participatingNodes": 100,
+ "nonParticipatingNodes": 100
+ },
+ "region": "us-east-2"
+ }
+ ]
+}