summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Jannotti <john.jannotti@algorand.com>2022-01-28 00:06:19 -0500
committerGitHub <noreply@github.com>2022-01-28 00:06:19 -0500
commitca0676419868a915257a46b779c62ccabf384c92 (patch)
tree7e2c61dcf3f175462d034bfdfa6caffbb9015311
parent7baef22f00045ae0db6376a1a2b7f62526700e0c (diff)
pooling costs, 3 opcodes
Fix pooled costs during app to app recursion Also adds divw, itxnas, gitxnas Makes accessing 0 accounts in dry-run ok.
-rw-r--r--cmd/opdoc/opdoc.go5
-rw-r--r--daemon/algod/api/server/v2/dryrun.go22
-rw-r--r--daemon/algod/api/server/v2/dryrun_test.go99
-rw-r--r--data/transactions/logic/README.md7
-rw-r--r--data/transactions/logic/README_in.md3
-rw-r--r--data/transactions/logic/TEAL_opcodes.md35
-rw-r--r--data/transactions/logic/assembler.go122
-rw-r--r--data/transactions/logic/assembler_test.go8
-rw-r--r--data/transactions/logic/blackbox_test.go2
-rw-r--r--data/transactions/logic/doc.go10
-rw-r--r--data/transactions/logic/doc_test.go4
-rw-r--r--data/transactions/logic/eval.go125
-rw-r--r--data/transactions/logic/evalAppTxn_test.go6
-rw-r--r--data/transactions/logic/evalStateful_test.go13
-rw-r--r--data/transactions/logic/eval_test.go36
-rw-r--r--data/transactions/logic/opcodes.go34
-rw-r--r--ledger/internal/eval_blackbox_test.go8
-rwxr-xr-xtest/scripts/e2e_subs/e2e-app-extra-pages.sh2
18 files changed, 332 insertions, 209 deletions
diff --git a/cmd/opdoc/opdoc.go b/cmd/opdoc/opdoc.go
index b226b31a3..7167583d3 100644
--- a/cmd/opdoc/opdoc.go
+++ b/cmd/opdoc/opdoc.go
@@ -35,7 +35,10 @@ func opGroupMarkdownTable(names []string, out io.Writer) {
opSpecs := logic.OpsByName[logic.LogicVersion]
// TODO: sort by logic.OpSpecs[].Opcode
for _, opname := range names {
- spec := opSpecs[opname]
+ spec, ok := opSpecs[opname]
+ if !ok {
+ continue // Allows "future" opcodes to exist, but be omitted from spec.
+ }
fmt.Fprintf(out, "| `%s%s` | %s |\n",
markdownTableEscape(spec.Name), immediateMarkdown(&spec),
markdownTableEscape(logic.OpDoc(opname)))
diff --git a/daemon/algod/api/server/v2/dryrun.go b/daemon/algod/api/server/v2/dryrun.go
index ef7ed19e2..d8a675d77 100644
--- a/daemon/algod/api/server/v2/dryrun.go
+++ b/daemon/algod/api/server/v2/dryrun.go
@@ -263,11 +263,9 @@ func (dl *dryrunLedger) CheckDup(config.ConsensusParams, basics.Round, basics.Ro
func (dl *dryrunLedger) LookupWithoutRewards(rnd basics.Round, addr basics.Address) (basics.AccountData, basics.Round, error) {
// check accounts from debug records uploaded
- any := false
out := basics.AccountData{}
accti, ok := dl.accountsIn[addr]
if ok {
- any = true
acct := dl.dr.Accounts[accti]
var err error
if out, err = AccountToAccountData(&acct); err != nil {
@@ -280,7 +278,6 @@ func (dl *dryrunLedger) LookupWithoutRewards(rnd basics.Round, addr basics.Addre
}
appi, ok := dl.accountApps[addr]
if ok {
- any = true
app := dl.dr.Apps[appi]
params, err := ApplicationParamsToAppParams(&app.Params)
if err != nil {
@@ -299,9 +296,9 @@ func (dl *dryrunLedger) LookupWithoutRewards(rnd basics.Round, addr basics.Addre
}
}
}
- if !any {
- return basics.AccountData{}, 0, fmt.Errorf("no account for addr %s", addr.String())
- }
+ // Returns a 0 account for account that wasn't supplied. This is new as of
+ // AVM 1.1 timeframe, but seems correct (allows using app accounts, and the
+ // fee sink without supplying them)
return out, rnd, nil
}
@@ -376,13 +373,13 @@ func doDryrunRequest(dr *DryrunRequest, response *generated.DryrunResponse) {
proto.EnableAppCostPooling = true
// allow a huge execution budget
- maxCurrentBudget := uint64(proto.MaxAppProgramCost * 100)
+ maxCurrentBudget := proto.MaxAppProgramCost * 100
pooledAppBudget := maxCurrentBudget
- allowedBudget := uint64(0)
- cumulativeCost := uint64(0)
+ allowedBudget := 0
+ cumulativeCost := 0
for _, stxn := range dr.Txns {
if stxn.Txn.Type == protocol.ApplicationCallTx {
- allowedBudget += uint64(proto.MaxAppProgramCost)
+ allowedBudget += proto.MaxAppProgramCost
}
}
ep.PooledApplicationBudget = &pooledAppBudget
@@ -507,7 +504,7 @@ func doDryrunRequest(dr *DryrunRequest, response *generated.DryrunResponse) {
cost := maxCurrentBudget - pooledAppBudget
if pass {
if !origEnableAppCostPooling {
- if cost > uint64(proto.MaxAppProgramCost) {
+ if cost > proto.MaxAppProgramCost {
pass = false
err = fmt.Errorf("cost budget exceeded: budget is %d but program cost was %d", proto.MaxAppProgramCost, cost)
}
@@ -516,7 +513,8 @@ func doDryrunRequest(dr *DryrunRequest, response *generated.DryrunResponse) {
err = fmt.Errorf("cost budget exceeded: budget is %d but program cost was %d", allowedBudget-cumulativeCost, cost)
}
}
- result.Cost = &cost
+ cost64 := uint64(cost)
+ result.Cost = &cost64
maxCurrentBudget = pooledAppBudget
cumulativeCost += cost
diff --git a/daemon/algod/api/server/v2/dryrun_test.go b/daemon/algod/api/server/v2/dryrun_test.go
index a65f306e3..f5c850e31 100644
--- a/daemon/algod/api/server/v2/dryrun_test.go
+++ b/daemon/algod/api/server/v2/dryrun_test.go
@@ -33,6 +33,7 @@ import (
"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/algorand/go-algorand/test/partitiontest"
)
@@ -1365,35 +1366,24 @@ int 1`
sender, err := basics.UnmarshalChecksumAddress("47YPQTIGQEO7T4Y4RWDYWEKV6RTR2UNBQXBABEEGM72ESWDQNCQ52OPASU")
a.NoError(err)
- app, err := basics.UnmarshalChecksumAddress("6BPQU5WNZMTO4X72A2THZCGNJNTTE7YL6AWCYSUUTZEIYMJSEPJCQQ6DQI")
- a.NoError(err)
// make balance records
appIdx := basics.AppIndex(100)
dr := DryrunRequest{
- Txns: []transactions.SignedTxn{{
- Txn: transactions.Transaction{
- Type: protocol.ApplicationCallTx,
- Header: transactions.Header{
- Sender: sender,
- Fee: basics.MicroAlgos{Raw: 100},
- Note: []byte{1, 2, 3},
- },
- ApplicationCallTxnFields: transactions.ApplicationCallTxnFields{
- ApplicationID: appIdx,
- },
+ ProtocolVersion: string(dryrunProtoVersion),
+ Txns: []transactions.SignedTxn{txntest.Txn{
+ Type: protocol.ApplicationCallTx,
+ Sender: sender,
+ ApplicationID: appIdx,
+ }.SignedTxn()},
+ Apps: []generated.Application{{
+ Id: uint64(appIdx),
+ Params: generated.ApplicationParams{
+ Creator: sender.String(),
+ ApprovalProgram: approval,
+ ClearStateProgram: clst,
},
}},
- Apps: []generated.Application{
- {
- Id: uint64(appIdx),
- Params: generated.ApplicationParams{
- Creator: sender.String(),
- ApprovalProgram: approval,
- ClearStateProgram: clst,
- },
- },
- },
Accounts: []generated.Account{
{
Address: sender.String(),
@@ -1402,23 +1392,16 @@ int 1`
AmountWithoutPendingRewards: 10000000,
},
{
- Address: app.String(),
+ Address: appIdx.Address().String(),
Status: "Offline",
Amount: 10000000,
AmountWithoutPendingRewards: 10000000,
},
- {
- Address: basics.Address{}.String(),
- Status: "Offline",
- },
},
}
- dr.ProtocolVersion = string(dryrunProtoVersion)
-
var response generated.DryrunResponse
doDryrunRequest(&dr, &response)
- require.NoError(t, err)
checkAppCallPass(t, &response)
if t.Failed() {
logResponse(t, &response)
@@ -1485,3 +1468,57 @@ int 0
logResponse(t, &response)
}
}
+
+func TestDryrunInnerPay(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ a := require.New(t)
+
+ paySender, err := logic.AssembleString(`
+#pragma version 5
+itxn_begin
+int pay
+itxn_field TypeEnum
+txn Sender
+itxn_field Receiver
+int 10
+itxn_field Amount
+itxn_submit
+int 1
+`)
+ require.NoError(t, err)
+
+ ops, err := logic.AssembleString("int 1")
+ clst := ops.Program
+ require.NoError(t, err)
+
+ sender, err := basics.UnmarshalChecksumAddress("47YPQTIGQEO7T4Y4RWDYWEKV6RTR2UNBQXBABEEGM72ESWDQNCQ52OPASU")
+ a.NoError(err)
+
+ appIdx := basics.AppIndex(7)
+ dr := DryrunRequest{
+ ProtocolVersion: string(dryrunProtoVersion),
+ Txns: []transactions.SignedTxn{txntest.Txn{
+ Type: protocol.ApplicationCallTx,
+ Sender: sender,
+ ApplicationID: appIdx,
+ }.SignedTxn()},
+ Apps: []generated.Application{{
+ Id: uint64(appIdx),
+ Params: generated.ApplicationParams{
+ ApprovalProgram: paySender.Program,
+ ClearStateProgram: clst,
+ },
+ }},
+ // Sender must exist (though no fee is ever taken)
+ // AppAccount must exist and be able to pay the inner fee and the pay amount (but min balance not checked)
+ Accounts: []generated.Account{
+ {Address: sender.String(), Status: "Offline"}, // sender
+ {Address: appIdx.Address().String(), Status: "Offline", AmountWithoutPendingRewards: 1_010}}, // app account
+ }
+ var response generated.DryrunResponse
+ doDryrunRequest(&dr, &response)
+ checkAppCallPass(t, &response)
+ if t.Failed() {
+ logResponse(t, &response)
+ }
+}
diff --git a/data/transactions/logic/README.md b/data/transactions/logic/README.md
index 68c4126e5..104a0d661 100644
--- a/data/transactions/logic/README.md
+++ b/data/transactions/logic/README.md
@@ -179,7 +179,8 @@ and `byte 0xcafed00d`. Constants introduced via `int` and `byte` will
be assembled into appropriate uses of `pushint|pushbytes` and
`{int|byte}c, {int|byte}c_[0123]` to minimize program size.
-The opcodes intcblock and bytecblock use [proto-buf style variable length unsigned int](https://developers.google.com/protocol-buffers/docs/encoding#varint),
+
+The opcodes `intcblock` and `bytecblock` use [proto-buf style variable length unsigned int](https://developers.google.com/protocol-buffers/docs/encoding#varint),
reproduced [here](#varuint). The `intcblock` opcode is followed by a
varuint specifying the number of integer constants and then that
number of varuints. The `bytecblock` opcode is followed by a varuint
@@ -277,6 +278,7 @@ return stack matches the name of the input value.
| `~` | bitwise invert value A |
| `mulw` | A times B as a 128-bit result in two uint64s. X is the high 64 bits, Y is the low |
| `addw` | A plus B as a 128-bit result. X is the carry-bit, Y is the low-order 64 bits. |
+| `divw` | A,B / C. Fail if C == 0 or if result overflows. |
| `divmodw` | W,X = (A,B / C,D); Y,Z = (A,B modulo C,D) |
| `expw` | A raised to the Bth power as a 128-bit result in two uint64s. X is the high 64 bits, Y is the low. Fail if A == B == 0 or if the results exceeds 2^128-1 |
| `getbit` | Bth bit of (byte-array or integer) A. |
@@ -296,7 +298,6 @@ return stack matches the name of the input value.
| `extract_uint16` | A uint16 formed from a range of big-endian bytes from A starting at B up to but not including B+2. If B+2 is larger than the array length, the program fails |
| `extract_uint32` | A uint32 formed from a range of big-endian bytes from A starting at B up to but not including B+4. If B+4 is larger than the array length, the program fails |
| `extract_uint64` | A uint64 formed from a range of big-endian bytes from A starting at B up to but not including B+8. If B+8 is larger than the array length, the program fails |
-| `base64_decode e` | decode A which was base64-encoded using _encoding_ E. Fail if A is not base64 encoded with encoding E |
The following opcodes take byte-array values that are interpreted as
big-endian unsigned integers. For mathematical operators, the
@@ -626,8 +627,10 @@ different transaction types, are rejected by `itxn_submit`.
| `itxn_submit` | execute the current inner transaction group. Fail if executing this group would exceed the inner transaction limit, or if any transaction in the group fails. |
| `itxn f` | field F of the last inner transaction |
| `itxna f i` | Ith value of the array field F of the last inner transaction |
+| `itxnas f` | Ath value of the array field F of the last inner transaction |
| `gitxn t f` | field F of the Tth transaction in the last inner group submitted |
| `gitxna t f i` | Ith value of the array field F from the Tth transaction in the last inner group submitted |
+| `gitxnas t f` | Ath value of the array field F from the Tth transaction in the last inner group submitted |
# Assembler Syntax
diff --git a/data/transactions/logic/README_in.md b/data/transactions/logic/README_in.md
index 8e81b7afb..fb86cf3f8 100644
--- a/data/transactions/logic/README_in.md
+++ b/data/transactions/logic/README_in.md
@@ -179,7 +179,8 @@ and `byte 0xcafed00d`. Constants introduced via `int` and `byte` will
be assembled into appropriate uses of `pushint|pushbytes` and
`{int|byte}c, {int|byte}c_[0123]` to minimize program size.
-The opcodes intcblock and bytecblock use [proto-buf style variable length unsigned int](https://developers.google.com/protocol-buffers/docs/encoding#varint),
+
+The opcodes `intcblock` and `bytecblock` use [proto-buf style variable length unsigned int](https://developers.google.com/protocol-buffers/docs/encoding#varint),
reproduced [here](#varuint). The `intcblock` opcode is followed by a
varuint specifying the number of integer constants and then that
number of varuints. The `bytecblock` opcode is followed by a varuint
diff --git a/data/transactions/logic/TEAL_opcodes.md b/data/transactions/logic/TEAL_opcodes.md
index e3291f405..94ecee800 100644
--- a/data/transactions/logic/TEAL_opcodes.md
+++ b/data/transactions/logic/TEAL_opcodes.md
@@ -770,16 +770,6 @@ When A is a uint64, index 0 is the least significant bit. Setting bit 3 to 1 on
- A uint64 formed from a range of big-endian bytes from A starting at B up to but not including B+8. If B+8 is larger than the array length, the program fails
- Availability: v5
-## base64_decode e
-
-- Opcode: 0x5c {uint8 encoding index}
-- Stack: ..., A: []byte &rarr; ..., []byte
-- decode A which was base64-encoded using _encoding_ E. Fail if A is not base64 encoded with encoding E
-- **Cost**: 25
-- Availability: v6
-
-Decodes A using the base64 encoding E. Specify the encoding with an immediate arg either as URL and Filename Safe (`URLEncoding`) or Standard (`StdEncoding`). See <a href="https://rfc-editor.org/rfc/rfc4648.html#section-4">RFC 4648</a> (sections 4 and 5). It is assumed that the encoding ends with the exact number of `=` padding characters as required by the RFC. When padding occurs, any unused pad bits in the encoding must be set to zero or the decoding will fail. The special cases of `\n` and `\r` are allowed but completely ignored. An error will result when attempting to decode a string with a character that is not in the encoding alphabet or not one of `=`, `\r`, or `\n`.
-
## balance
- Opcode: 0x60
@@ -1070,6 +1060,15 @@ bitlen interprets arrays as big-endian integers, unlike setbit/getbit
- **Cost**: 40
- Availability: v6
+## divw
+
+- Opcode: 0x97
+- Stack: ..., A: uint64, B: uint64, C: uint64 &rarr; ..., uint64
+- A,B / C. Fail if C == 0 or if result overflows.
+- Availability: v6
+
+The notation A,B indicates that A and B are interpreted as a uint128 value, with A as the high uint64 and B the low.
+
## b+
- Opcode: 0xa0
@@ -1309,3 +1308,19 @@ bitlen interprets arrays as big-endian integers, unlike setbit/getbit
- Bth scratch space value of the Ath transaction in the current group
- Availability: v6
- Mode: Application
+
+## itxnas f
+
+- Opcode: 0xc5 {uint8 transaction field index}
+- Stack: ..., A: uint64 &rarr; ..., any
+- Ath value of the array field F of the last inner transaction
+- Availability: v6
+- Mode: Application
+
+## gitxnas t f
+
+- Opcode: 0xc6 {uint8 transaction group index} {uint8 transaction field index}
+- Stack: ..., A: uint64 &rarr; ..., any
+- Ath value of the array field F from the Tth transaction in the last inner group submitted
+- Availability: v6
+- Mode: Application
diff --git a/data/transactions/logic/assembler.go b/data/transactions/logic/assembler.go
index cdf91df68..5d5af3dec 100644
--- a/data/transactions/logic/assembler.go
+++ b/data/transactions/logic/assembler.go
@@ -807,7 +807,7 @@ func simpleImm(value string, label string) (uint64, error) {
return res, err
}
-func assembleTxn(ops *OpStream, spec *OpSpec, args []string) error {
+func asmTxn(ops *OpStream, spec *OpSpec, args []string) error {
if len(args) != 1 {
return ops.error("txn expects one argument")
}
@@ -821,21 +821,22 @@ func assembleTxn(ops *OpStream, spec *OpSpec, args []string) error {
return nil
}
-// assembleTxn2 delegates to assembleTxn or assembleTxna depending on number of operands
-func assembleTxn2(ops *OpStream, spec *OpSpec, args []string) error {
+// asmTxn2 delegates to asmTxn or asmTxna depending on number of operands
+func asmTxn2(ops *OpStream, spec *OpSpec, args []string) error {
if len(args) == 1 {
- return assembleTxn(ops, spec, args)
+ return asmTxn(ops, spec, args)
}
if len(args) == 2 {
txna := OpsByName[ops.Version]["txna"]
- return assembleTxna(ops, &txna, args)
+ return asmTxna(ops, &txna, args)
}
return ops.error("txn expects one or two arguments")
}
-func assembleTxna(ops *OpStream, spec *OpSpec, args []string) error {
+// asmTxna also assemble asmItxna
+func asmTxna(ops *OpStream, spec *OpSpec, args []string) error {
if len(args) != 2 {
- return ops.error("txna expects two immediate arguments")
+ return ops.errorf("%s expects two immediate arguments", spec.Name)
}
fs, err := txnFieldImm(args[0], true, ops)
if err != nil {
@@ -853,9 +854,10 @@ func assembleTxna(ops *OpStream, spec *OpSpec, args []string) error {
return nil
}
-func assembleTxnas(ops *OpStream, spec *OpSpec, args []string) error {
+// asmTxnas also assembles itxnas
+func asmTxnas(ops *OpStream, spec *OpSpec, args []string) error {
if len(args) != 1 {
- return ops.error("txnas expects one immediate argument")
+ return ops.errorf("%s expects one immediate argument", spec.Name)
}
fs, err := txnFieldImm(args[0], true, ops)
if err != nil {
@@ -868,9 +870,9 @@ func assembleTxnas(ops *OpStream, spec *OpSpec, args []string) error {
return nil
}
-func assembleGtxn(ops *OpStream, spec *OpSpec, args []string) error {
+func asmGtxn(ops *OpStream, spec *OpSpec, args []string) error {
if len(args) != 2 {
- return ops.error("gtxn expects two arguments")
+ return ops.errorf("%s expects two arguments", spec.Name)
}
slot, err := simpleImm(args[0], "transaction index")
if err != nil {
@@ -888,18 +890,19 @@ func assembleGtxn(ops *OpStream, spec *OpSpec, args []string) error {
return nil
}
-func assembleGtxn2(ops *OpStream, spec *OpSpec, args []string) error {
+func asmGtxn2(ops *OpStream, spec *OpSpec, args []string) error {
if len(args) == 2 {
- return assembleGtxn(ops, spec, args)
+ return asmGtxn(ops, spec, args)
}
if len(args) == 3 {
gtxna := OpsByName[ops.Version]["gtxna"]
- return assembleGtxna(ops, &gtxna, args)
+ return asmGtxna(ops, &gtxna, args)
}
return ops.errorf("%s expects two or three arguments", spec.Name)
}
-func assembleGtxna(ops *OpStream, spec *OpSpec, args []string) error {
+//asmGtxna also assembles asmGitxna
+func asmGtxna(ops *OpStream, spec *OpSpec, args []string) error {
if len(args) != 3 {
return ops.errorf("%s expects three arguments", spec.Name)
}
@@ -924,7 +927,8 @@ func assembleGtxna(ops *OpStream, spec *OpSpec, args []string) error {
return nil
}
-func assembleGtxnas(ops *OpStream, spec *OpSpec, args []string) error {
+// asmGtxnas also assembles gitxnas
+func asmGtxnas(ops *OpStream, spec *OpSpec, args []string) error {
if len(args) != 2 {
return ops.errorf("%s expects two immediate arguments", spec.Name)
}
@@ -944,10 +948,10 @@ func assembleGtxnas(ops *OpStream, spec *OpSpec, args []string) error {
return nil
}
-func assembleGtxns(ops *OpStream, spec *OpSpec, args []string) error {
+func asmGtxns(ops *OpStream, spec *OpSpec, args []string) error {
if len(args) == 2 {
gtxnsa := OpsByName[ops.Version]["gtxnsa"]
- return assembleGtxnsa(ops, &gtxnsa, args)
+ return asmGtxnsa(ops, &gtxnsa, args)
}
if len(args) != 1 {
return ops.errorf("%s expects one or two immediate arguments", spec.Name)
@@ -963,7 +967,7 @@ func assembleGtxns(ops *OpStream, spec *OpSpec, args []string) error {
return nil
}
-func assembleGtxnsa(ops *OpStream, spec *OpSpec, args []string) error {
+func asmGtxnsa(ops *OpStream, spec *OpSpec, args []string) error {
if len(args) != 2 {
return ops.errorf("%s expects two immediate arguments", spec.Name)
}
@@ -982,7 +986,7 @@ func assembleGtxnsa(ops *OpStream, spec *OpSpec, args []string) error {
return nil
}
-func assembleGtxnsas(ops *OpStream, spec *OpSpec, args []string) error {
+func asmGtxnsas(ops *OpStream, spec *OpSpec, args []string) error {
if len(args) != 1 {
return ops.errorf("%s expects one immediate argument", spec.Name)
}
@@ -1003,7 +1007,7 @@ func asmItxn(ops *OpStream, spec *OpSpec, args []string) error {
}
if len(args) == 2 {
itxna := OpsByName[ops.Version]["itxna"]
- return asmItxna(ops, &itxna, args)
+ return asmTxna(ops, &itxna, args)
}
return ops.errorf("%s expects one or two arguments", spec.Name)
}
@@ -1023,84 +1027,18 @@ func asmItxnOnly(ops *OpStream, spec *OpSpec, args []string) error {
return nil
}
-func asmItxna(ops *OpStream, spec *OpSpec, args []string) error {
- if len(args) != 2 {
- return ops.errorf("%s expects two immediate arguments", spec.Name)
- }
- fs, err := txnFieldImm(args[0], true, ops)
- if err != nil {
- return ops.errorf("%s %w", spec.Name, err)
- }
- arrayFieldIdx, err := simpleImm(args[1], "array index")
- if err != nil {
- return ops.errorf("%s %w", spec.Name, err)
- }
-
- ops.pending.WriteByte(spec.Opcode)
- ops.pending.WriteByte(uint8(fs.field))
- ops.pending.WriteByte(uint8(arrayFieldIdx))
- ops.returns(fs.ftype)
- return nil
-}
-
-// asmGitxn delegates to asmGitxnOnly or asmGitxna depending on number of operands
+// asmGitxn delegates to asmGtxn or asmGtxna depending on number of operands
func asmGitxn(ops *OpStream, spec *OpSpec, args []string) error {
if len(args) == 2 {
- return asmGitxnOnly(ops, spec, args)
+ return asmGtxn(ops, spec, args)
}
if len(args) == 3 {
itxna := OpsByName[ops.Version]["gitxna"]
- return asmGitxna(ops, &itxna, args)
+ return asmGtxna(ops, &itxna, args)
}
return ops.errorf("%s expects two or three arguments", spec.Name)
}
-func asmGitxnOnly(ops *OpStream, spec *OpSpec, args []string) error {
- if len(args) != 2 {
- return ops.errorf("%s expects two arguments", spec.Name)
- }
- slot, err := simpleImm(args[0], "transaction index")
- if err != nil {
- return ops.errorf("%s %w", spec.Name, err)
- }
- fs, err := txnFieldImm(args[1], false, ops)
- if err != nil {
- return ops.errorf("%s %w", spec.Name, err)
- }
-
- ops.pending.WriteByte(spec.Opcode)
- ops.pending.WriteByte(uint8(slot))
- ops.pending.WriteByte(uint8(fs.field))
- ops.returns(fs.ftype)
- return nil
-}
-
-func asmGitxna(ops *OpStream, spec *OpSpec, args []string) error {
- if len(args) != 3 {
- return ops.errorf("%s expects three immediate arguments", spec.Name)
- }
- slot, err := simpleImm(args[0], "transaction index")
- if err != nil {
- return ops.errorf("%s %w", spec.Name, err)
- }
-
- fs, err := txnFieldImm(args[1], true, ops)
- if err != nil {
- return ops.errorf("%s %w", spec.Name, err)
- }
- arrayFieldIdx, err := simpleImm(args[2], "array index")
- if err != nil {
- return ops.errorf("%s %w", spec.Name, err)
- }
-
- ops.pending.WriteByte(spec.Opcode)
- ops.pending.WriteByte(uint8(slot))
- ops.pending.WriteByte(uint8(fs.field))
- ops.pending.WriteByte(uint8(arrayFieldIdx))
- ops.returns(fs.ftype)
- return nil
-}
-
func assembleGlobal(ops *OpStream, spec *OpSpec, args []string) error {
if len(args) != 1 {
return ops.errorf("%s expects one argument", spec.Name)
@@ -2513,7 +2451,7 @@ func checkPushBytes(cx *EvalContext) error {
return cx.err
}
-// This is also used to disassemble gtxns, gtxnsas, txnas, itxn
+// This is also used to disassemble gtxns, gtxnsas, txnas, itxn, itxnas
func disTxn(dis *disassembleState, spec *OpSpec) (string, error) {
lastIdx := dis.pc + 1
if len(dis.program) <= lastIdx {
@@ -2544,7 +2482,7 @@ func disTxna(dis *disassembleState, spec *OpSpec) (string, error) {
return fmt.Sprintf("%s %s %d", spec.Name, TxnFieldNames[txarg], arrayFieldIdx), nil
}
-// disGtxn is also used to disassemble gtxnas, gitxn
+// disGtxn is also used to disassemble gtxnas, gitxn, gitxnas
func disGtxn(dis *disassembleState, spec *OpSpec) (string, error) {
lastIdx := dis.pc + 2
if len(dis.program) <= lastIdx {
diff --git a/data/transactions/logic/assembler_test.go b/data/transactions/logic/assembler_test.go
index fd53d458b..0758e5b2e 100644
--- a/data/transactions/logic/assembler_test.go
+++ b/data/transactions/logic/assembler_test.go
@@ -346,7 +346,6 @@ const v6Nonsense = v5Nonsense + `
itxn_next
gitxn 4 CreatedAssetID
gitxna 3 Logs 12
-base64_decode URLEncoding
int 0
dup
gloadss
@@ -354,6 +353,11 @@ byte 0x0123456789abcd
bsqrt
txn Sender
acct_params_get AcctBalance
+pushint 8; pushint 8; pushint 32; divw // use pushint to prevent changes to intcblock choices
+pushint 1
+itxnas Logs
+pushint 1
+gitxnas 0 Logs
`
var nonsense = map[uint64]string{
@@ -371,7 +375,7 @@ var compiled = map[uint64]string{
3: "032008b7a60cf8acd19181cf959a12f8acd19181cf951af8acd19181cf15f8acd191810f01020026050212340c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d024242047465737400320032013202320328292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e0102222324252104082209240a220b230c240d250e230f23102311231223132314181b1c2b171615400003290349483403350222231d4a484848482a50512a63222352410003420000432105602105612105270463484821052b62482b642b65484821052b2106662b21056721072b682b692107210570004848210771004848361c0037001a0031183119311b311d311e311f3120210721051e312131223123312431253126312731283129312a312b312c312d312e312f4478222105531421055427042106552105082106564c4d4b02210538212106391c0081e80780046a6f686e",
4: "042004010200b7a60c26040242420c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f23102311231223132314181b1c28171615400003290349483403350222231d4a484848482a50512a632223524100034200004322602261222b634848222862482864286548482228236628226724286828692422700048482471004848361c0037001a0031183119311b311d311e311f312024221e312131223123312431253126312731283129312a312b312c312d312e312f44782522531422542b2355220823564c4d4b0222382123391c0081e80780046a6f686e2281d00f24231f880003420001892223902291922394239593a0a1a2a3a4a5a6a7a8a9aaabacadae23af3a00003b003c003d8164",
5: "052004010002b7a60c26050242420c68656c6c6f20776f726c6421070123456789abcd208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292b0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f23102311231223132314181b1c28171615400003290349483403350222231d4a484848482b50512a632223524100034200004322602261222704634848222862482864286548482228246628226723286828692322700048482371004848361c0037001a0031183119311b311d311e311f312023221e312131223123312431253126312731283129312a312b312c312d312e312f447825225314225427042455220824564c4d4b0222382124391c0081e80780046a6f686e2281d00f23241f880003420001892224902291922494249593a0a1a2a3a4a5a6a7a8a9aaabacadae24af3a00003b003c003d816472064e014f012a57000823810858235b235a2359b03139330039b1b200b322c01a23c1001a2323c21a23c3233e233f8120af06002a494905002a49490700b53a03",
- 6: "062004010002b7a60c26050242420c68656c6c6f20776f726c6421070123456789abcd208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292b0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f23102311231223132314181b1c28171615400003290349483403350222231d4a484848482b50512a632223524100034200004322602261222704634848222862482864286548482228246628226723286828692322700048482371004848361c0037001a0031183119311b311d311e311f312023221e312131223123312431253126312731283129312a312b312c312d312e312f447825225314225427042455220824564c4d4b0222382124391c0081e80780046a6f686e2281d00f23241f880003420001892224902291922494249593a0a1a2a3a4a5a6a7a8a9aaabacadae24af3a00003b003c003d816472064e014f012a57000823810858235b235a2359b03139330039b1b200b322c01a23c1001a2323c21a23c3233e233f8120af06002a494905002a49490700b53a03b6b7043cb8033a0c5c002349c42a9631007300",
+ 6: "062004010002b7a60c26050242420c68656c6c6f20776f726c6421070123456789abcd208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292b0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f23102311231223132314181b1c28171615400003290349483403350222231d4a484848482b50512a632223524100034200004322602261222704634848222862482864286548482228246628226723286828692322700048482371004848361c0037001a0031183119311b311d311e311f312023221e312131223123312431253126312731283129312a312b312c312d312e312f447825225314225427042455220824564c4d4b0222382124391c0081e80780046a6f686e2281d00f23241f880003420001892224902291922494249593a0a1a2a3a4a5a6a7a8a9aaabacadae24af3a00003b003c003d816472064e014f012a57000823810858235b235a2359b03139330039b1b200b322c01a23c1001a2323c21a23c3233e233f8120af06002a494905002a49490700b53a03b6b7043cb8033a0c2349c42a9631007300810881088120978101c53a8101c6003a",
}
func pseudoOp(opcode string) bool {
diff --git a/data/transactions/logic/blackbox_test.go b/data/transactions/logic/blackbox_test.go
index dc6fdd968..d9a01f02e 100644
--- a/data/transactions/logic/blackbox_test.go
+++ b/data/transactions/logic/blackbox_test.go
@@ -87,7 +87,7 @@ func TestNewAppEvalParams(t *testing.T) {
if reflect.DeepEqual(param, config.Consensus[protocol.ConsensusV29]) {
require.Nil(t, ep.PooledApplicationBudget)
} else if reflect.DeepEqual(param, config.Consensus[protocol.ConsensusFuture]) {
- require.Equal(t, *ep.PooledApplicationBudget, uint64(param.MaxAppProgramCost*testCase.numAppCalls))
+ require.Equal(t, *ep.PooledApplicationBudget, param.MaxAppProgramCost*testCase.numAppCalls)
}
})
}
diff --git a/data/transactions/logic/doc.go b/data/transactions/logic/doc.go
index e33460adb..c7ac55212 100644
--- a/data/transactions/logic/doc.go
+++ b/data/transactions/logic/doc.go
@@ -60,6 +60,7 @@ var opDocByName = map[string]string{
"expw": "A raised to the Bth power as a 128-bit result in two uint64s. X is the high 64 bits, Y is the low. Fail if A == B == 0 or if the results exceeds 2^128-1",
"mulw": "A times B as a 128-bit result in two uint64s. X is the high 64 bits, Y is the low",
"addw": "A plus B as a 128-bit result. X is the carry-bit, Y is the low-order 64 bits.",
+ "divw": "A,B / C. Fail if C == 0 or if result overflows.",
"divmodw": "W,X = (A,B / C,D); Y,Z = (A,B modulo C,D)",
"intcblock": "prepare block of uint64 constants for use by intc",
@@ -95,8 +96,10 @@ var opDocByName = map[string]string{
"gtxnsas": "Bth value of the array field F from the Ath transaction in the current group",
"itxn": "field F of the last inner transaction",
"itxna": "Ith value of the array field F of the last inner transaction",
+ "itxnas": "Ath value of the array field F of the last inner transaction",
"gitxn": "field F of the Tth transaction in the last inner group submitted",
"gitxna": "Ith value of the array field F from the Tth transaction in the last inner group submitted",
+ "gitxnas": "Ath value of the array field F from the Tth transaction in the last inner group submitted",
"global": "global field F",
"load": "Ith scratch space value. All scratch spaces are 0 at program start.",
@@ -231,8 +234,10 @@ var opcodeImmediateNotes = map[string]string{
"itxn_field": "{uint8 transaction field index}",
"itxn": "{uint8 transaction field index}",
"itxna": "{uint8 transaction field index} {uint8 transaction field array index}",
+ "itxnas": "{uint8 transaction field index}",
"gitxn": "{uint8 transaction group index} {uint8 transaction field index}",
"gitxna": "{uint8 transaction group index} {uint8 transaction field index} {uint8 transaction field array index}",
+ "gitxnas": "{uint8 transaction group index} {uint8 transaction field index}",
"ecdsa_verify": "{uint8 curve index}",
"ecdsa_pk_decompress": "{uint8 curve index}",
@@ -278,6 +283,7 @@ var opDocExtras = map[string]string{
"+": "Overflow is an error condition which halts execution and fails the transaction. Full precision is available from `addw`.",
"/": "`divmodw` is available to divide the two-element values produced by `mulw` and `addw`.",
"bitlen": "bitlen interprets arrays as big-endian integers, unlike setbit/getbit",
+ "divw": "The notation A,B indicates that A and B are interpreted as a uint128 value, with A as the high uint64 and B the low.",
"divmodw": "The notation J,K indicates that two uint64 values J and K are interpreted as a uint128 value, with J as the high uint64 and K the low.",
"txn": "FirstValidTime causes the program to fail. The field is reserved for future use.",
"gtxn": "for notes on transaction fields available, see `txn`. If this transaction is _i_ in the group, `gtxn i field` is equivalent to `txn field`.",
@@ -322,14 +328,14 @@ func OpDocExtra(opName string) string {
// here is the order args opcodes are presented, so place related
// 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"},
+ "Arithmetic": {"sha256", "keccak256", "sha512_256", "ed25519verify", "ecdsa_verify", "ecdsa_pk_recover", "ecdsa_pk_decompress", "+", "-", "/", "*", "<", ">", "<=", ">=", "&&", "||", "shl", "shr", "sqrt", "bitlen", "exp", "==", "!=", "!", "len", "itob", "btoi", "%", "|", "&", "^", "~", "mulw", "addw", "divw", "divmodw", "expw", "getbit", "setbit", "getbyte", "setbyte", "concat"},
"Byte Array Manipulation": {"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%", "bsqrt"},
"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"},
"Flow Control": {"err", "bnz", "bz", "b", "return", "pop", "dup", "dup2", "dig", "cover", "uncover", "swap", "select", "assert", "callsub", "retsub"},
"State Access": {"balance", "min_balance", "app_opted_in", "app_local_get", "app_local_get_ex", "app_global_get", "app_global_get_ex", "app_local_put", "app_global_put", "app_local_del", "app_global_del", "asset_holding_get", "asset_params_get", "app_params_get", "acct_params_get", "log"},
- "Inner Transactions": {"itxn_begin", "itxn_next", "itxn_field", "itxn_submit", "itxn", "itxna", "gitxn", "gitxna"},
+ "Inner Transactions": {"itxn_begin", "itxn_next", "itxn_field", "itxn_submit", "itxn", "itxna", "itxnas", "gitxn", "gitxna", "gitxnas"},
}
// OpCost indicates the cost of an operation over the range of
diff --git a/data/transactions/logic/doc_test.go b/data/transactions/logic/doc_test.go
index 3287755f6..85b97aaad 100644
--- a/data/transactions/logic/doc_test.go
+++ b/data/transactions/logic/doc_test.go
@@ -77,7 +77,7 @@ func TestOpGroupCoverage(t *testing.T) {
for _, name := range names {
_, exists := opsSeen[name]
if !exists {
- t.Errorf("error: op %#v in group list but not in OpSpecs\n", name)
+ t.Errorf("op %#v in group list but not in OpSpecs\n", name)
continue
}
opsSeen[name] = true
@@ -85,7 +85,7 @@ func TestOpGroupCoverage(t *testing.T) {
}
for name, seen := range opsSeen {
if !seen {
- t.Errorf("warning: op %#v not in any group of OpGroups\n", name)
+ t.Errorf("op %#v not in any group of OpGroups\n", name)
}
}
}
diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go
index 26d234855..4cacc6396 100644
--- a/data/transactions/logic/eval.go
+++ b/data/transactions/logic/eval.go
@@ -268,7 +268,7 @@ type EvalParams struct {
Specials *transactions.SpecialAddresses
// Total pool of app call budget in a group transaction (nil before budget pooling enabled)
- PooledApplicationBudget *uint64
+ PooledApplicationBudget *int
// Total allowable inner txns in a group transaction (nil before inner pooling enabled)
pooledAllowedInners *int
@@ -309,14 +309,14 @@ func NewEvalParams(txgroup []transactions.SignedTxnWithAD, proto *config.Consens
minTealVersion := ComputeMinTealVersion(txgroup, false)
- var pooledApplicationBudget *uint64
+ var pooledApplicationBudget *int
var pooledAllowedInners *int
credit, _ := transactions.FeeCredit(txgroup, proto.MinTxnFee)
if proto.EnableAppCostPooling {
- pooledApplicationBudget = new(uint64)
- *pooledApplicationBudget = uint64(apps * proto.MaxAppProgramCost)
+ pooledApplicationBudget = new(int)
+ *pooledApplicationBudget = apps * proto.MaxAppProgramCost
}
if proto.EnableInnerTransactionPooling {
@@ -348,12 +348,13 @@ func NewInnerEvalParams(txg []transactions.SignedTxn, caller *EvalContext) *Eval
if minTealVersion < *caller.MinTealVersion {
minTealVersion = *caller.MinTealVersion
}
- // Unlike NewEvalParams, do not add credit here. opTxSubmit has already done so.
+
+ // Unlike NewEvalParams, do not add fee credit here. opTxSubmit has already done so.
if caller.Proto.EnableAppCostPooling {
for _, tx := range txgroup {
if tx.Txn.Type == protocol.ApplicationCallTx {
- *caller.PooledApplicationBudget += uint64(caller.Proto.MaxAppProgramCost)
+ *caller.PooledApplicationBudget += caller.Proto.MaxAppProgramCost
}
}
}
@@ -558,10 +559,6 @@ func EvalContract(program []byte, gi int, aid basics.AppIndex, params *EvalParam
}
pass, err := eval(program, &cx)
- // update pooled budget (shouldn't overflow, but being careful anyway)
- if cx.PooledApplicationBudget != nil {
- *cx.PooledApplicationBudget = basics.SubSaturate(*cx.PooledApplicationBudget, uint64(cx.cost))
- }
// update side effects
cx.pastScratch[cx.GroupIndex] = &scratchSpace{}
*cx.pastScratch[cx.GroupIndex] = cx.scratch
@@ -721,7 +718,7 @@ func check(program []byte, params *EvalParams, mode runMode) (err error) {
cx.branchTargets = make(map[int]bool)
cx.instructionStarts = make(map[int]bool)
- maxCost := cx.budget()
+ maxCost := cx.remainingBudget()
if version >= backBranchEnabledVersion {
maxCost = math.MaxInt32
}
@@ -796,14 +793,14 @@ func boolToUint(x bool) uint64 {
// MaxStackDepth should not change unless gated by a teal version change / consensus upgrade.
const MaxStackDepth = 1000
-func (cx *EvalContext) budget() int {
+func (cx *EvalContext) remainingBudget() int {
if cx.runModeFlags == runModeSignature {
- return int(cx.Proto.LogicSigMaxCost)
+ return int(cx.Proto.LogicSigMaxCost) - cx.cost
}
- if cx.Proto.EnableAppCostPooling && cx.PooledApplicationBudget != nil {
- return int(*cx.PooledApplicationBudget)
+ if cx.PooledApplicationBudget != nil {
+ return *cx.PooledApplicationBudget
}
- return cx.Proto.MaxAppProgramCost
+ return cx.Proto.MaxAppProgramCost - cx.cost
}
func (cx *EvalContext) remainingInners() int {
@@ -850,9 +847,13 @@ func (cx *EvalContext) step() {
return
}
cx.cost += deets.Cost
- if cx.cost > cx.budget() {
- cx.err = fmt.Errorf("pc=%3d dynamic cost budget exceeded, executing %s: remaining budget is %d but program cost was %d",
- cx.pc, spec.Name, cx.budget(), cx.cost)
+ if cx.PooledApplicationBudget != nil {
+ *cx.PooledApplicationBudget -= deets.Cost
+ }
+
+ if cx.remainingBudget() < 0 {
+ cx.err = fmt.Errorf("pc=%3d dynamic cost budget exceeded, executing %s: local program cost was %d",
+ cx.pc, spec.Name, cx.cost)
return
}
@@ -1160,6 +1161,28 @@ func opMulw(cx *EvalContext) {
cx.stack[last].Uint = low
}
+func opDivw(cx *EvalContext) {
+ last := len(cx.stack) - 1
+ prev := last - 1
+ pprev := last - 2
+ hi := cx.stack[pprev].Uint
+ lo := cx.stack[prev].Uint
+ y := cx.stack[last].Uint
+ // These two clauses catch what will cause panics in bits.Div64, so we get
+ // nicer errors.
+ if y == 0 {
+ cx.err = errors.New("divw 0")
+ return
+ }
+ if y <= hi {
+ cx.err = fmt.Errorf("divw overflow: %d <= %d", y, hi)
+ return
+ }
+ quo, _ := bits.Div64(hi, lo, y)
+ cx.stack = cx.stack[:prev] // pop 2
+ cx.stack[pprev].Uint = quo
+}
+
func opLt(cx *EvalContext) {
last := len(cx.stack) - 1
prev := last - 1
@@ -2462,6 +2485,30 @@ func opItxna(cx *EvalContext) {
cx.stack = append(cx.stack, sv)
}
+func opItxnas(cx *EvalContext) {
+ fs, err := cx.fetchField(TxnField(cx.program[cx.pc+1]), true)
+ if err != nil {
+ cx.err = err
+ return
+ }
+
+ last := len(cx.stack) - 1
+ arrayFieldIdx := cx.stack[last].Uint
+
+ if len(cx.Txn.EvalDelta.InnerTxns) == 0 {
+ cx.err = fmt.Errorf("no inner transaction available %d", fs.field)
+ return
+ }
+
+ itxn := &cx.Txn.EvalDelta.InnerTxns[len(cx.Txn.EvalDelta.InnerTxns)-1]
+ sv, err := cx.txnFieldToStack(itxn, fs, arrayFieldIdx, 0, true)
+ if err != nil {
+ cx.err = err
+ return
+ }
+ cx.stack[last] = sv
+}
+
func (cx *EvalContext) getLastInnerGroup() []transactions.SignedTxnWithAD {
inners := cx.Txn.EvalDelta.InnerTxns
// If there are no inners yet, return empty slice, which will result in error
@@ -2529,6 +2576,30 @@ func opGitxna(cx *EvalContext) {
cx.stack = append(cx.stack, sv)
}
+func opGitxnas(cx *EvalContext) {
+ lastInnerGroup := cx.getLastInnerGroup()
+ gi := int(cx.program[cx.pc+1])
+ if gi >= len(lastInnerGroup) {
+ cx.err = fmt.Errorf("gitxnas %d ... but last group has %d", gi, len(lastInnerGroup))
+ return
+ }
+ itxn := &lastInnerGroup[gi]
+
+ fs, err := cx.fetchField(TxnField(cx.program[cx.pc+2]), true)
+ if err != nil {
+ cx.err = err
+ return
+ }
+ last := len(cx.stack) - 1
+ arrayFieldIdx := cx.stack[last].Uint
+ sv, err := cx.txnFieldToStack(itxn, fs, arrayFieldIdx, gi, true)
+ if err != nil {
+ cx.err = err
+ return
+ }
+ cx.stack[last] = sv
+}
+
func opGaidImpl(cx *EvalContext, gi int, opName string) (sv stackValue, err error) {
if gi >= len(cx.TxnGroup) {
err = fmt.Errorf("%s lookup TxnGroup[%d] but it only has %d", opName, gi, len(cx.TxnGroup))
@@ -2669,7 +2740,7 @@ func (cx *EvalContext) globalFieldToValue(fs globalFieldSpec) (sv stackValue, er
case GroupID:
sv.Bytes = cx.Txn.Txn.Group[:]
case OpcodeBudget:
- sv.Uint = uint64(cx.budget() - cx.cost)
+ sv.Uint = uint64(cx.remainingBudget())
case CallerApplicationID:
if cx.caller != nil {
sv.Uint = uint64(cx.caller.appID)
@@ -3942,12 +4013,15 @@ func opLog(cx *EvalContext) {
cx.stack = cx.stack[:last]
}
-func authorizedSender(cx *EvalContext, addr basics.Address) bool {
+func authorizedSender(cx *EvalContext, addr basics.Address) error {
authorizer, err := cx.Ledger.Authorizer(addr)
if err != nil {
- return false
+ return err
}
- return cx.getApplicationAddress(cx.appID) == authorizer
+ if cx.getApplicationAddress(cx.appID) != authorizer {
+ return fmt.Errorf("app %d (addr %s) unauthorized %s", cx.appID, cx.getApplicationAddress(cx.appID), authorizer)
+ }
+ return nil
}
// addInnerTxn appends a fresh SignedTxn to subtxns, populated with reasonable
@@ -4386,8 +4460,9 @@ func opTxSubmit(cx *EvalContext) {
// transaction pool. Namely that any transaction that makes it
// to Perform (which is equivalent to eval.applyTransaction)
// is authorized, and WellFormed.
- if !authorizedSender(cx, cx.subtxns[itx].Txn.Sender) {
- cx.err = fmt.Errorf("unauthorized")
+ err := authorizedSender(cx, cx.subtxns[itx].Txn.Sender)
+ if err != nil {
+ cx.err = err
return
}
diff --git a/data/transactions/logic/evalAppTxn_test.go b/data/transactions/logic/evalAppTxn_test.go
index 39058d0bc..5a239955a 100644
--- a/data/transactions/logic/evalAppTxn_test.go
+++ b/data/transactions/logic/evalAppTxn_test.go
@@ -1133,6 +1133,12 @@ byte 0x33
==
assert
+int 0
+gitxnas 1 Logs
+byte 0x33
+==
+assert
+
itxn_begin
int appl; itxn_field TypeEnum
int 444; itxn_field ApplicationID
diff --git a/data/transactions/logic/evalStateful_test.go b/data/transactions/logic/evalStateful_test.go
index 447b18b67..cafc496fd 100644
--- a/data/transactions/logic/evalStateful_test.go
+++ b/data/transactions/logic/evalStateful_test.go
@@ -2231,6 +2231,12 @@ func TestReturnTypes(t *testing.T) {
StackBytes: "byte 0x33343536\n",
}
ep, tx, ledger := makeSampleEnv()
+
+ // This unit test reususes this `ep` willy-nilly. Would be nice to rewrite,
+ // but for now, trun off budget pooling so that it doesn't get exhausted.
+ ep.Proto.EnableAppCostPooling = false
+ ep.PooledApplicationBudget = nil
+
tx.Type = protocol.ApplicationCallTx
tx.ApplicationID = 1
tx.ForeignApps = []basics.AppIndex{tx.ApplicationID}
@@ -2304,11 +2310,14 @@ func TestReturnTypes(t *testing.T) {
"txnas": "txnas ApplicationArgs",
"gtxnas": "gtxnas 0 ApplicationArgs",
"gtxnsas": "pop; pop; int 0; int 0; gtxnsas ApplicationArgs",
+ "divw": "pop; pop; pop; int 1; int 2; int 3; divw",
"args": "args",
"itxn": "itxn_begin; int pay; itxn_field TypeEnum; itxn_submit; itxn CreatedAssetID",
"itxna": "itxn_begin; int pay; itxn_field TypeEnum; itxn_submit; itxna Accounts 0",
+ "itxnas": "itxn_begin; int pay; itxn_field TypeEnum; itxn_submit; itxnas Accounts",
"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",
+ "gitxnas": "itxn_begin; int pay; itxn_field TypeEnum; itxn_submit; gitxnas 0 Accounts",
"base64_decode": `pushbytes "YWJjMTIzIT8kKiYoKSctPUB+"; base64_decode StdEncoding; pushbytes "abc123!?$*&()'-=@~"; ==; pushbytes "YWJjMTIzIT8kKiYoKSctPUB-"; base64_decode URLEncoding; pushbytes "abc123!?$*&()'-=@~"; ==; &&; assert`,
}
@@ -2361,7 +2370,7 @@ func TestReturnTypes(t *testing.T) {
require.Equal(
t,
len(spec.Returns), len(cx.stack),
- fmt.Sprintf("\n%s%s expected to return %d values but stack is %v", ep.Trace.String(), spec.Name, len(spec.Returns), cx.stack),
+ fmt.Sprintf("\n%s%s expected to return %d values but stack is %#v", ep.Trace, spec.Name, len(spec.Returns), cx.stack),
)
for i := 0; i < len(spec.Returns); i++ {
sp := len(cx.stack) - 1 - i
@@ -2434,7 +2443,7 @@ func TestPooledAppCallsVerifyOp(t *testing.T) {
call := transactions.SignedTxn{Txn: transactions.Transaction{Type: protocol.ApplicationCallTx}}
// Simulate test with 2 grouped txn
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"})
+ Expect{0, "pc=107 dynamic cost budget exceeded, executing ed25519verify: local program cost was 1905"})
// Simulate test with 3 grouped txn
testApps(t, []string{source, "", ""}, []transactions.SignedTxn{call, call, call}, LogicVersion, ledger)
diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go
index df367c27d..c7f703b9f 100644
--- a/data/transactions/logic/eval_test.go
+++ b/data/transactions/logic/eval_test.go
@@ -107,9 +107,9 @@ func benchmarkEvalParams(txn *transactions.SignedTxn) *EvalParams {
ep := defaultEvalParamsWithVersion(txn, LogicVersion)
ep.Trace = nil // Tracing would slow down benchmarks
clone := *ep.Proto
- bigBudget := uint64(1000 * 1000) // Allow long run times
- clone.LogicSigMaxCost = bigBudget
- clone.MaxAppProgramCost = int(bigBudget)
+ bigBudget := 1000 * 1000 // Allow long run times
+ clone.LogicSigMaxCost = uint64(bigBudget)
+ clone.MaxAppProgramCost = bigBudget
ep.Proto = &clone
ep.PooledApplicationBudget = &bigBudget
return ep
@@ -136,7 +136,7 @@ func defaultEvalParamsWithVersion(txn *transactions.SignedTxn, version uint64) *
// a group, and then thrown away.
func (ep *EvalParams) reset() {
if ep.Proto.EnableAppCostPooling {
- budget := uint64(ep.Proto.MaxAppProgramCost)
+ budget := ep.Proto.MaxAppProgramCost
ep.PooledApplicationBudget = &budget
}
if ep.Proto.EnableInnerTransactionPooling {
@@ -574,6 +574,19 @@ int 1 // ret 1
`, 2)
}
+func TestDivw(t *testing.T) {
+ partitiontest.PartitionTest(t)
+
+ t.Parallel()
+ testPanics(t, "int 1; int 2; int 0; divw; assert;", 6)
+ testPanics(t, "int 2; int 1; int 1; divw; assert;", 6)
+ testPanics(t, "int 2; int 0; int 2; divw; assert", 6)
+ testAccepts(t, "int 1; int 2; int 2; divw;", 6)
+
+ testAccepts(t, "int 1; int 0; int 2; divw; int 0x8000000000000000; ==", 6)
+ testAccepts(t, "int 0; int 90; int 30; divw; int 3; ==", 6)
+}
+
func TestUint128(t *testing.T) {
partitiontest.PartitionTest(t)
@@ -3302,6 +3315,7 @@ func BenchmarkUintMath(b *testing.B) {
{"mul", "", "int 212; int 323; *; pop", "int 1"},
{"mulw", "", "int 21276237623; int 32387238723; mulw; pop; pop", "int 1"},
{"div", "", "int 736247364; int 892; /; pop", "int 1"},
+ {"divw", "", "int 736; int 892; int 892; divw; pop", "int 1"},
{"divmodw", "", "int 736247364; int 892; int 126712; int 71672; divmodw; pop; pop; pop; pop", "int 1"},
{"sqrt", "", "int 736247364; sqrt; pop", "int 1"},
{"exp", "", "int 734; int 5; exp; pop", "int 1"},
@@ -4633,10 +4647,18 @@ By Herman Melville`, "",
source := fmt.Sprintf(template, hex.EncodeToString([]byte(tc.decoded)), hex.EncodeToString([]byte(tc.encoded)), tc.alph)
if tc.error == "" {
- testAccepts(t, source, minB64DecodeVersion)
+ if LogicVersion < fidoVersion {
+ testProg(t, source, AssemblerMaxVersion, Expect{0, "unknown opcode..."})
+ } else {
+ testAccepts(t, source, fidoVersion)
+ }
} else {
- err := testPanics(t, source, minB64DecodeVersion)
- require.Contains(t, err.Error(), tc.error)
+ if LogicVersion < fidoVersion {
+ testProg(t, source, AssemblerMaxVersion, Expect{0, "unknown opcode..."})
+ } else {
+ err := testPanics(t, source, fidoVersion)
+ require.Contains(t, err.Error(), tc.error)
+ }
}
}
}
diff --git a/data/transactions/logic/opcodes.go b/data/transactions/logic/opcodes.go
index 19855ba9d..59ccf9b3f 100644
--- a/data/transactions/logic/opcodes.go
+++ b/data/transactions/logic/opcodes.go
@@ -55,6 +55,9 @@ const txnEffectsVersion = 6
// the Foreign arrays.
const createdResourcesVersion = 6
+// "Future" opcodes
+const fidoVersion = LogicVersion + 1 // base64, json, secp256r1
+
// opDetails records details such as non-standard costs, immediate
// arguments, or dynamic layout controlled by a check function.
type opDetails struct {
@@ -208,21 +211,21 @@ var OpSpecs = []OpSpec{
{0x2e, "arg_1", opArg1, asmDefault, disDefault, nil, oneBytes, 1, runModeSignature, opDefault},
{0x2f, "arg_2", opArg2, asmDefault, disDefault, nil, oneBytes, 1, runModeSignature, opDefault},
{0x30, "arg_3", opArg3, asmDefault, disDefault, nil, oneBytes, 1, runModeSignature, opDefault},
- {0x31, "txn", opTxn, assembleTxn, disTxn, nil, oneAny, 1, modeAny, immediates("f")},
+ {0x31, "txn", opTxn, asmTxn, disTxn, nil, oneAny, 1, modeAny, immediates("f")},
// It is ok to have the same opcode for different TEAL versions.
// This 'txn' asm command supports additional argument in version 2 and
// generates 'txna' opcode in that particular case
- {0x31, "txn", opTxn, assembleTxn2, disTxn, nil, oneAny, 2, modeAny, immediates("f")},
+ {0x31, "txn", opTxn, asmTxn2, disTxn, nil, oneAny, 2, modeAny, immediates("f")},
{0x32, "global", opGlobal, assembleGlobal, disGlobal, nil, oneAny, 1, modeAny, immediates("f")},
- {0x33, "gtxn", opGtxn, assembleGtxn, disGtxn, nil, oneAny, 1, modeAny, immediates("t", "f")},
- {0x33, "gtxn", opGtxn, assembleGtxn2, disGtxn, nil, oneAny, 2, modeAny, immediates("t", "f")},
+ {0x33, "gtxn", opGtxn, asmGtxn, disGtxn, nil, oneAny, 1, modeAny, immediates("t", "f")},
+ {0x33, "gtxn", opGtxn, asmGtxn2, disGtxn, nil, oneAny, 2, modeAny, immediates("t", "f")},
{0x34, "load", opLoad, asmDefault, disDefault, nil, oneAny, 1, modeAny, immediates("i")},
{0x35, "store", opStore, asmDefault, disDefault, oneAny, nil, 1, modeAny, immediates("i")},
- {0x36, "txna", opTxna, assembleTxna, disTxna, nil, oneAny, 2, modeAny, immediates("f", "i")},
- {0x37, "gtxna", opGtxna, assembleGtxna, disGtxna, nil, oneAny, 2, modeAny, immediates("t", "f", "i")},
+ {0x36, "txna", opTxna, asmTxna, disTxna, nil, oneAny, 2, modeAny, immediates("f", "i")},
+ {0x37, "gtxna", opGtxna, asmGtxna, disGtxna, nil, oneAny, 2, modeAny, immediates("t", "f", "i")},
// Like gtxn, but gets txn index from stack, rather than immediate arg
- {0x38, "gtxns", opGtxns, assembleGtxns, disTxn, oneInt, oneAny, 3, modeAny, immediates("f")},
- {0x39, "gtxnsa", opGtxnsa, assembleGtxns, disTxna, oneInt, oneAny, 3, modeAny, immediates("f", "i")},
+ {0x38, "gtxns", opGtxns, asmGtxns, disTxn, oneInt, oneAny, 3, modeAny, immediates("f")},
+ {0x39, "gtxnsa", opGtxnsa, asmGtxns, disTxna, oneInt, oneAny, 3, modeAny, immediates("f", "i")},
// Group scratch space access
{0x3a, "gload", opGload, asmDefault, disDefault, nil, oneAny, 4, runModeApplication, immediates("t", "i")},
{0x3b, "gloads", opGloads, asmDefault, disDefault, oneInt, oneAny, 4, runModeApplication, immediates("i")},
@@ -263,7 +266,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")},
+ {0x5c, "base64_decode", opBase64Decode, assembleBase64Decode, disBase64Decode, oneBytes, oneBytes, fidoVersion, modeAny, costlyImm(25, "e")},
{0x60, "balance", opBalance, asmDefault, disDefault, oneInt, oneInt, 2, runModeApplication, opDefault},
{0x60, "balance", opBalance, asmDefault, disDefault, oneAny, oneInt, directRefEnabledVersion, runModeApplication, opDefault},
@@ -308,6 +311,7 @@ var OpSpecs = []OpSpec{
{0x94, "exp", opExp, asmDefault, disDefault, twoInts, oneInt, 4, modeAny, opDefault},
{0x95, "expw", opExpw, asmDefault, disDefault, twoInts, twoInts, 4, modeAny, costly(10)},
{0x96, "bsqrt", opBytesSqrt, asmDefault, disDefault, oneBytes, oneBytes, 6, modeAny, costly(40)},
+ {0x97, "divw", opDivw, asmDefault, disDefault, twoInts.plus(oneInt), oneInt, 6, modeAny, opDefault},
// Byteslice math.
{0xa0, "b+", opBytesPlus, asmDefault, disDefault, twoBytes, oneBytes, 4, modeAny, costly(10)},
@@ -333,17 +337,19 @@ var OpSpecs = []OpSpec{
{0xb2, "itxn_field", opTxField, asmTxField, disTxField, oneAny, nil, 5, runModeApplication, stacky(typeTxField, "f")},
{0xb3, "itxn_submit", opTxSubmit, asmDefault, disDefault, nil, nil, 5, runModeApplication, opDefault},
{0xb4, "itxn", opItxn, asmItxn, disTxn, nil, oneAny, 5, runModeApplication, immediates("f")},
- {0xb5, "itxna", opItxna, asmItxna, disTxna, nil, oneAny, 5, runModeApplication, immediates("f", "i")},
+ {0xb5, "itxna", opItxna, asmTxna, disTxna, nil, oneAny, 5, runModeApplication, immediates("f", "i")},
{0xb6, "itxn_next", opTxNext, asmDefault, disDefault, nil, nil, 6, runModeApplication, opDefault},
{0xb7, "gitxn", opGitxn, asmGitxn, disGtxn, nil, oneAny, 6, runModeApplication, immediates("t", "f")},
- {0xb8, "gitxna", opGitxna, asmGitxna, disGtxna, nil, oneAny, 6, runModeApplication, immediates("t", "f", "i")},
+ {0xb8, "gitxna", opGitxna, asmGtxna, disGtxna, nil, oneAny, 6, runModeApplication, immediates("t", "f", "i")},
// Dynamic indexing
- {0xc0, "txnas", opTxnas, assembleTxnas, disTxn, oneInt, oneAny, 5, modeAny, immediates("f")},
- {0xc1, "gtxnas", opGtxnas, assembleGtxnas, disGtxn, oneInt, oneAny, 5, modeAny, immediates("t", "f")},
- {0xc2, "gtxnsas", opGtxnsas, assembleGtxnsas, disTxn, twoInts, oneAny, 5, modeAny, immediates("f")},
+ {0xc0, "txnas", opTxnas, asmTxnas, disTxn, oneInt, oneAny, 5, modeAny, immediates("f")},
+ {0xc1, "gtxnas", opGtxnas, asmGtxnas, disGtxn, oneInt, oneAny, 5, modeAny, immediates("t", "f")},
+ {0xc2, "gtxnsas", opGtxnsas, asmGtxnsas, disTxn, twoInts, oneAny, 5, modeAny, immediates("f")},
{0xc3, "args", opArgs, asmDefault, disDefault, oneInt, oneBytes, 5, runModeSignature, opDefault},
{0xc4, "gloadss", opGloadss, asmDefault, disDefault, twoInts, oneAny, 6, runModeApplication, opDefault},
+ {0xc5, "itxnas", opItxnas, asmTxnas, disTxn, oneInt, oneAny, 6, runModeApplication, immediates("f")},
+ {0xc6, "gitxnas", opGitxnas, asmGtxnas, disGtxn, oneInt, oneAny, 6, runModeApplication, immediates("t", "f")},
}
type sortByOpcode []OpSpec
diff --git a/ledger/internal/eval_blackbox_test.go b/ledger/internal/eval_blackbox_test.go
index 0ac18c44c..b468bda83 100644
--- a/ledger/internal/eval_blackbox_test.go
+++ b/ledger/internal/eval_blackbox_test.go
@@ -585,14 +585,14 @@ func TestEvalAppPooledBudgetWithTxnGroup(t *testing.T) {
"",
""},
{source(5, 48), false, true,
- "pc=157 dynamic cost budget exceeded, executing pushint: remaining budget is 700 but program cost was 701",
+ "pc=157 dynamic cost budget exceeded, executing pushint",
""},
{source(16, 17), false, true,
- "pc= 12 dynamic cost budget exceeded, executing keccak256: remaining budget is 700 but program cost was 781",
+ "pc= 12 dynamic cost budget exceeded, executing keccak256",
""},
{source(16, 18), false, false,
- "pc= 12 dynamic cost budget exceeded, executing keccak256: remaining budget is 700 but program cost was 781",
- "pc= 78 dynamic cost budget exceeded, executing pushint: remaining budget is 2100 but program cost was 2101"},
+ "pc= 12 dynamic cost budget exceeded, executing keccak256",
+ "pc= 78 dynamic cost budget exceeded, executing pushint"},
}
for i, param := range params {
diff --git a/test/scripts/e2e_subs/e2e-app-extra-pages.sh b/test/scripts/e2e_subs/e2e-app-extra-pages.sh
index 4852918c6..54732deb8 100755
--- a/test/scripts/e2e_subs/e2e-app-extra-pages.sh
+++ b/test/scripts/e2e_subs/e2e-app-extra-pages.sh
@@ -77,7 +77,7 @@ fi
# App create with extra pages, v4 teal
RES=$(${gcmd} app create --creator ${ACCOUNT} --approval-prog "${BIG_TEAL_V4_FILE}" --clear-prog "${BIG_TEAL_V4_FILE}" --extra-pages 3 --global-byteslices 1 --global-ints 0 --local-byteslices 0 --local-ints 0 2>&1 || true)
-EXPERROR="pc=704 dynamic cost budget exceeded, executing intc_0: remaining budget is 700 but program cost was 701"
+EXPERROR="pc=704 dynamic cost budget exceeded, executing intc_0: local program cost was 701"
if [[ $RES != *"${EXPERROR}"* ]]; then
date '+app-extra-pages-test FAIL the application creation should fail %Y%m%d_%H%M%S'
false