diff --git a/data/transactions/logic/README.md b/data/transactions/logic/README.md index a5279327c4..0015bafec0 100644 --- a/data/transactions/logic/README.md +++ b/data/transactions/logic/README.md @@ -341,6 +341,9 @@ Global fields are fields that are common to all the transactions in the group. I | 9 | CreatorAddress | []byte | Address of the creator of the current application. Fails if no such application is executing. LogicSigVersion >= 3. | | 10 | CurrentApplicationAddress | []byte | Address that the current application controls. Fails in LogicSigs. LogicSigVersion >= 5. | | 11 | GroupID | []byte | ID of the transaction group. 32 zero bytes if the transaction is not part of a group. LogicSigVersion >= 5. | +| 12 | OpcodeBudget | uint64 | The remaining cost that can be spent by opcodes in this program. LogicSigVersion >= 6. | +| 13 | CallerApplicationID | uint64 | The application ID of the application that called this application. 0 if this application is at the top-level. LogicSigVersion >= 6. | +| 14 | CallerApplicationAddress | []byte | The application address of the application that called this application. ZeroAddress if this application is at the top-level. LogicSigVersion >= 6. | **Asset Fields** diff --git a/data/transactions/logic/TEAL_opcodes.md b/data/transactions/logic/TEAL_opcodes.md index df6db7e3f2..66ecafe7f3 100644 --- a/data/transactions/logic/TEAL_opcodes.md +++ b/data/transactions/logic/TEAL_opcodes.md @@ -524,6 +524,9 @@ FirstValidTime causes the program to fail. The field is reserved for future use. | 9 | CreatorAddress | []byte | Address of the creator of the current application. Fails if no such application is executing. LogicSigVersion >= 3. | | 10 | CurrentApplicationAddress | []byte | Address that the current application controls. Fails in LogicSigs. LogicSigVersion >= 5. | | 11 | GroupID | []byte | ID of the transaction group. 32 zero bytes if the transaction is not part of a group. LogicSigVersion >= 5. | +| 12 | OpcodeBudget | uint64 | The remaining cost that can be spent by opcodes in this program. LogicSigVersion >= 6. | +| 13 | CallerApplicationID | uint64 | The application ID of the application that called this application. 0 if this application is at the top-level. LogicSigVersion >= 6. | +| 14 | CallerApplicationAddress | []byte | The application address of the application that called this application. ZeroAddress if this application is at the top-level. LogicSigVersion >= 6. | ## gtxn t f diff --git a/data/transactions/logic/assembler_test.go b/data/transactions/logic/assembler_test.go index 10947245cc..152f218718 100644 --- a/data/transactions/logic/assembler_test.go +++ b/data/transactions/logic/assembler_test.go @@ -1315,6 +1315,9 @@ global LatestTimestamp global CurrentApplicationID global CreatorAddress global GroupID +global OpcodeBudget +global CallerApplicationID +global CallerApplicationAddress txn Sender txn Fee bnz label1 diff --git a/data/transactions/logic/doc.go b/data/transactions/logic/doc.go index 6b836760e9..43ba8d2178 100644 --- a/data/transactions/logic/doc.go +++ b/data/transactions/logic/doc.go @@ -463,6 +463,9 @@ var globalFieldDocs = map[string]string{ "CreatorAddress": "Address of the creator of the current application. Fails if no such application is executing", "CurrentApplicationAddress": "Address that the current application controls. Fails in LogicSigs", "GroupID": "ID of the transaction group. 32 zero bytes if the transaction is not part of a group.", + "OpcodeBudget": "The remaining cost that can be spent by opcodes in this program.", + "CallerApplicationID": "The application ID of the application that called this application. 0 if this application is at the top-level.", + "CallerApplicationAddress": "The application address of the application that called this application. ZeroAddress if this application is at the top-level.", } // GlobalFieldDocs are notes on fields available in `global` with extra versioning info if any diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 10af327d69..3285884112 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -2636,6 +2636,22 @@ func (cx *EvalContext) globalFieldToValue(fs globalFieldSpec) (sv stackValue, er sv.Bytes, err = cx.getCreatorAddress() case GroupID: sv.Bytes = cx.Txn.Txn.Group[:] + case OpcodeBudget: + sv.Uint = uint64(cx.budget() - cx.cost) + case CallerApplicationID: + if cx.caller != nil { + sv.Uint, err = cx.caller.getApplicationID() + } else { + sv.Uint = 0 + } + case CallerApplicationAddress: + if cx.caller != nil { + var addr basics.Address + addr, err = cx.caller.getApplicationAddress() + sv.Bytes = addr[:] + } else { + sv.Bytes = zeroAddress[:] + } default: err = fmt.Errorf("invalid global field %d", fs.field) } diff --git a/data/transactions/logic/evalAppTxn_test.go b/data/transactions/logic/evalAppTxn_test.go index 5cd6728c3f..69bb35c9e9 100644 --- a/data/transactions/logic/evalAppTxn_test.go +++ b/data/transactions/logic/evalAppTxn_test.go @@ -845,8 +845,8 @@ int 1 // 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 a little, so it's more -// like 690 or so. +// 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) @@ -874,6 +874,36 @@ itxn_submit; 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) + ledger.NewApp(tx.Receiver, 222, basics.AppParams{ + ApprovalProgram: gasup.Program, + }) + + source := ` +// 698, not 699, because intcblock happens first +global OpcodeBudget; int 698; ==; assert +// "buy" more +itxn_begin +int appl; itxn_field TypeEnum +int 222; itxn_field ApplicationID +itxn_submit; +global OpcodeBudget; int 1387; ==; assert +itxn_begin +int appl; itxn_field TypeEnum +int 222; itxn_field ApplicationID +itxn_submit; +global OpcodeBudget; int 2076; ==; assert +int 1 +` + + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + ledger.NewAccount(ledger.ApplicationID().Address(), 50_000) + tx.ForeignApps = []basics.AppIndex{basics.AppIndex(222)} + testApp(t, source, ep) +} + // TestInnerTxIDs confirms that TxIDs are available and different func TestInnerTxIDs(t *testing.T) { ep, tx, ledger := makeSampleEnv() @@ -1140,3 +1170,30 @@ int 890 == `, 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(` +global CallerApplicationID +int 888 +== +global CallerApplicationAddress +addr %s +== +&& +`, basics.AppIndex(888).Address()), AssemblerMaxVersion) + ledger.NewApp(tx.Receiver, 222, basics.AppParams{ + ApprovalProgram: globals.Program, + }) + + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + ledger.NewAccount(ledger.ApplicationID().Address(), 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 +int 1 +`, ep) +} diff --git a/data/transactions/logic/evalStateful_test.go b/data/transactions/logic/evalStateful_test.go index 0d8345635b..34eabb135a 100644 --- a/data/transactions/logic/evalStateful_test.go +++ b/data/transactions/logic/evalStateful_test.go @@ -2419,3 +2419,18 @@ func TestAppAddress(t *testing.T) { source = fmt.Sprintf("int 0; app_params_get AppAddress; assert; addr %s; ==;", a) testApp(t, source, ep) } + +func TestBudget(t *testing.T) { + ep, tx, ledger := makeSampleEnv() + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + source := ` +global OpcodeBudget +int 699 +== +assert +global OpcodeBudget +int 695 +== +` + testApp(t, source, ep) +} diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index 0ca809264b..be4d4527db 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -930,7 +930,17 @@ byte 0x0706000000000000000000000000000000000000000000000000000000000000 ` const globalV6TestProgram = globalV5TestProgram + ` -// No new globals in v6 +global OpcodeBudget +int 0 +> +&& +global CallerApplicationAddress +global ZeroAddress +== +&& +global CallerApplicationID +! +&& ` func TestGlobal(t *testing.T) { @@ -941,6 +951,7 @@ func TestGlobal(t *testing.T) { lastField GlobalField program string } + // Associate the highest allowed global constant with each version's test program tests := map[uint64]desc{ 0: {GroupSize, globalV1TestProgram}, 1: {GroupSize, globalV1TestProgram}, @@ -948,10 +959,11 @@ func TestGlobal(t *testing.T) { 3: {CreatorAddress, globalV3TestProgram}, 4: {CreatorAddress, globalV4TestProgram}, 5: {GroupID, globalV5TestProgram}, - 6: {GroupID, globalV6TestProgram}, + 6: {CallerApplicationAddress, globalV6TestProgram}, } // tests keys are versions so they must be in a range 1..AssemblerMaxVersion plus zero version require.LessOrEqual(t, len(tests), AssemblerMaxVersion+1) + require.Len(t, globalFieldSpecs, int(invalidGlobalField)) ledger := MakeLedger(nil) addr, err := basics.UnmarshalChecksumAddress(testAddr) @@ -963,13 +975,13 @@ func TestGlobal(t *testing.T) { t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) { last := tests[v].lastField testProgram := tests[v].program - for _, globalField := range GlobalFieldNames[:last] { + for _, globalField := range GlobalFieldNames[:last+1] { if !strings.Contains(testProgram, globalField) { t.Errorf("TestGlobal missing field %v", globalField) } } - var txn transactions.SignedTxn + txn := transactions.SignedTxn{} txn.Txn.Group = crypto.Digest{0x07, 0x06} proto := config.ConsensusParams{ @@ -1024,9 +1036,7 @@ int %s == &&`, symbol, string(tt)) ops := testProg(t, text, v) - if v < appsEnabledVersion && tt == protocol.ApplicationCallTx { - } - var txn transactions.SignedTxn + txn := transactions.SignedTxn{} txn.Txn.Type = tt if v < appsEnabledVersion && tt == protocol.ApplicationCallTx { testLogicBytes(t, ops.Program, defaultEvalParams(&txn), diff --git a/data/transactions/logic/fields.go b/data/transactions/logic/fields.go index fe64dd1c80..da2b26e10d 100644 --- a/data/transactions/logic/fields.go +++ b/data/transactions/logic/fields.go @@ -371,6 +371,17 @@ const ( // GroupID [32]byte GroupID + // v6 + + // OpcodeBudget The remaining budget available for execution + OpcodeBudget + + // CallerApplicationID The ID of the caller app, else 0 + CallerApplicationID + + // CallerApplicationAddress The Address of the caller app, else ZeroAddress + CallerApplicationAddress + invalidGlobalField ) @@ -400,6 +411,9 @@ var globalFieldSpecs = []globalFieldSpec{ {CreatorAddress, StackBytes, runModeApplication, 3}, {CurrentApplicationAddress, StackBytes, runModeApplication, 5}, {GroupID, StackBytes, modeAny, 5}, + {OpcodeBudget, StackUint64, runModeApplication, 6}, + {CallerApplicationID, StackUint64, runModeApplication, 6}, + {CallerApplicationAddress, StackBytes, runModeApplication, 6}, } // GlobalFieldSpecByField maps GlobalField to spec diff --git a/data/transactions/logic/fields_string.go b/data/transactions/logic/fields_string.go index 82abacb941..31012932cd 100644 --- a/data/transactions/logic/fields_string.go +++ b/data/transactions/logic/fields_string.go @@ -99,12 +99,15 @@ func _() { _ = x[CreatorAddress-9] _ = x[CurrentApplicationAddress-10] _ = x[GroupID-11] - _ = x[invalidGlobalField-12] + _ = x[OpcodeBudget-12] + _ = x[CallerApplicationID-13] + _ = x[CallerApplicationAddress-14] + _ = x[invalidGlobalField-15] } -const _GlobalField_name = "MinTxnFeeMinBalanceMaxTxnLifeZeroAddressGroupSizeLogicSigVersionRoundLatestTimestampCurrentApplicationIDCreatorAddressCurrentApplicationAddressGroupIDinvalidGlobalField" +const _GlobalField_name = "MinTxnFeeMinBalanceMaxTxnLifeZeroAddressGroupSizeLogicSigVersionRoundLatestTimestampCurrentApplicationIDCreatorAddressCurrentApplicationAddressGroupIDOpcodeBudgetCallerApplicationIDCallerApplicationAddressinvalidGlobalField" -var _GlobalField_index = [...]uint8{0, 9, 19, 29, 40, 49, 64, 69, 84, 104, 118, 143, 150, 168} +var _GlobalField_index = [...]uint8{0, 9, 19, 29, 40, 49, 64, 69, 84, 104, 118, 143, 150, 162, 181, 205, 223} func (i GlobalField) String() string { if i >= GlobalField(len(_GlobalField_index)-1) { diff --git a/ledger/internal/apptxn_test.go b/ledger/internal/apptxn_test.go index e8ac199985..43a6fda7aa 100644 --- a/ledger/internal/apptxn_test.go +++ b/ledger/internal/apptxn_test.go @@ -475,7 +475,8 @@ func TestClawbackAction(t *testing.T) { Accounts: []basics.Address{addrs[0], addrs[1]}, } eval = nextBlock(t, l, true, nil) - txgroup(t, l, eval, &overpay, &clawmove) + err := txgroup(t, l, eval, &overpay, &clawmove) + require.NoError(t, err) endBlock(t, l, eval) amount, _ := holding(t, l, addrs[1], asaIndex)