diff --git a/config/consensus.go b/config/consensus.go index 3cade39115..2b98533977 100644 --- a/config/consensus.go +++ b/config/consensus.go @@ -539,11 +539,6 @@ type ConsensusParams struct { // EnableBoxRefNameError specifies that box ref names should be validated early EnableBoxRefNameError bool - // EnableUnnamedBoxAccessInNewApps allows newly created (in this group) apps to - // create boxes that were not named in a box ref. Each empty box ref in the - // group allows one such creation. - EnableUnnamedBoxAccessInNewApps bool - // ExcludeExpiredCirculation excludes expired stake from the total online stake // used by agreement for Circulation, and updates the calculation of StateProofOnlineTotalWeight used // by state proofs to use the same method (rather than excluding stake from the top N stakeholders as before). @@ -572,17 +567,16 @@ type ConsensusParams struct { // EnableSha512BlockHash adds an additional SHA-512 hash to the block header. EnableSha512BlockHash bool - // EnableInnerClawbackWithoutSenderHolding allows an inner clawback (axfer - // w/ AssetSender) even if the Sender holding of the asset is not - // available. This parameters can be removed and assumed true after the - // first consensus release in which it is set true. - EnableInnerClawbackWithoutSenderHolding bool - // AppSizeUpdates allows application update transactions to change // the extra-program-pages and global schema sizes. Since it enables newly // legal transactions, this parameter can be removed and assumed true after // the first consensus release in which it is set true. AppSizeUpdates bool + + // AllowZeroLocalAppRef allows for a 0 in a LocalRef of the access list to + // specify the current app. This parameter can be removed and assumed true + // after the first consensus release in which it is set true. + AllowZeroLocalAppRef bool } // ProposerPayoutRules puts several related consensus parameters in one place. The same @@ -1444,13 +1438,10 @@ func initConsensusProtocols() { v41.EnableAppVersioning = true v41.EnableSha512BlockHash = true - v41.EnableUnnamedBoxAccessInNewApps = true - // txn.Access work v41.MaxAppTxnAccounts = 8 // Accounts are no worse than others, they should be the same v41.MaxAppAccess = 16 // Twice as many, though cross products are explicit v41.BytesPerBoxReference = 2048 // Count is more important that bytes, loosen up - v41.EnableInnerClawbackWithoutSenderHolding = true v41.LogicSigMsig = false v41.LogicSigLMsig = true @@ -1469,6 +1460,7 @@ func initConsensusProtocols() { vFuture.LogicSigVersion = 13 // When moving this to a release, put a new higher LogicSigVersion here vFuture.AppSizeUpdates = true + vFuture.AllowZeroLocalAppRef = true Consensus[protocol.ConsensusFuture] = vFuture diff --git a/daemon/algod/api/server/v2/handlers.go b/daemon/algod/api/server/v2/handlers.go index b0f910f29a..78449fa7c2 100644 --- a/daemon/algod/api/server/v2/handlers.go +++ b/daemon/algod/api/server/v2/handlers.go @@ -1318,7 +1318,7 @@ func (v2 *Handlers) SimulateTransaction(ctx echo.Context, params model.SimulateT } } - response := convertSimulationResult(simulationResult, proto.EnableUnnamedBoxAccessInNewApps) + response := convertSimulationResult(simulationResult) handle, contentType, err := getCodecHandle((*string)(params.Format)) if err != nil { diff --git a/daemon/algod/api/server/v2/utils.go b/daemon/algod/api/server/v2/utils.go index b6e24464a6..c86985c2f5 100644 --- a/daemon/algod/api/server/v2/utils.go +++ b/daemon/algod/api/server/v2/utils.go @@ -473,13 +473,13 @@ func convertTxnTrace(txnTrace *simulation.TransactionTrace) *model.SimulationTra } } -func convertTxnResult(txnResult simulation.TxnResult, simplify bool) PreEncodedSimulateTxnResult { +func convertTxnResult(txnResult simulation.TxnResult) PreEncodedSimulateTxnResult { result := PreEncodedSimulateTxnResult{ Txn: ConvertInnerTxn(&txnResult.Txn), AppBudgetConsumed: omitEmpty(txnResult.AppBudgetConsumed), LogicSigBudgetConsumed: omitEmpty(txnResult.LogicSigBudgetConsumed), TransactionTrace: convertTxnTrace(txnResult.Trace), - UnnamedResourcesAccessed: convertUnnamedResourcesAccessed(txnResult.UnnamedResourcesAccessed, simplify), + UnnamedResourcesAccessed: convertUnnamedResourcesAccessed(txnResult.UnnamedResourcesAccessed), } if !txnResult.FixedSigner.IsZero() { @@ -490,13 +490,11 @@ func convertTxnResult(txnResult simulation.TxnResult, simplify bool) PreEncodedS return result } -func convertUnnamedResourcesAccessed(resources *simulation.ResourceTracker, simplify bool) *model.SimulateUnnamedResourcesAccessed { +func convertUnnamedResourcesAccessed(resources *simulation.ResourceTracker) *model.SimulateUnnamedResourcesAccessed { if resources == nil { return nil } - if simplify { - resources.Simplify() - } + resources.Simplify() return &model.SimulateUnnamedResourcesAccessed{ Accounts: sliceOrNil(stringSlice(slices.Collect(maps.Keys(resources.Accounts)))), Assets: sliceOrNil(slices.Collect(maps.Keys(resources.Assets))), @@ -568,18 +566,15 @@ func convertSimulateInitialStates(initialStates *simulation.ResourcesInitialStat } } -func convertTxnGroupResult(txnGroupResult simulation.TxnGroupResult, simplify bool) PreEncodedSimulateTxnGroupResult { - txnResults := make([]PreEncodedSimulateTxnResult, len(txnGroupResult.Txns)) - for i, txnResult := range txnGroupResult.Txns { - txnResults[i] = convertTxnResult(txnResult, simplify) - } +func convertTxnGroupResult(txnGroupResult simulation.TxnGroupResult) PreEncodedSimulateTxnGroupResult { + txnResults := util.Map(txnGroupResult.Txns, convertTxnResult) encoded := PreEncodedSimulateTxnGroupResult{ Txns: txnResults, FailureMessage: omitEmpty(txnGroupResult.FailureMessage), AppBudgetAdded: omitEmpty(txnGroupResult.AppBudgetAdded), AppBudgetConsumed: omitEmpty(txnGroupResult.AppBudgetConsumed), - UnnamedResourcesAccessed: convertUnnamedResourcesAccessed(txnGroupResult.UnnamedResourcesAccessed, simplify), + UnnamedResourcesAccessed: convertUnnamedResourcesAccessed(txnGroupResult.UnnamedResourcesAccessed), } if len(txnGroupResult.FailedAt) > 0 { @@ -590,7 +585,7 @@ func convertTxnGroupResult(txnGroupResult simulation.TxnGroupResult, simplify bo return encoded } -func convertSimulationResult(result simulation.Result, simplify bool) PreEncodedSimulateResponse { +func convertSimulationResult(result simulation.Result) PreEncodedSimulateResponse { var evalOverrides *model.SimulationEvalOverrides if result.EvalOverrides != (simulation.ResultEvalOverrides{}) { evalOverrides = &model.SimulationEvalOverrides{ @@ -604,11 +599,9 @@ func convertSimulationResult(result simulation.Result, simplify bool) PreEncoded } return PreEncodedSimulateResponse{ - Version: result.Version, - LastRound: result.LastRound, - TxnGroups: util.Map(result.TxnGroups, func(tg simulation.TxnGroupResult) PreEncodedSimulateTxnGroupResult { - return convertTxnGroupResult(tg, simplify) - }), + Version: result.Version, + LastRound: result.LastRound, + TxnGroups: util.Map(result.TxnGroups, convertTxnGroupResult), EvalOverrides: evalOverrides, ExecTraceConfig: result.TraceConfig, InitialStates: convertSimulateInitialStates(result.InitialStates), diff --git a/data/transactions/application.go b/data/transactions/application.go index 78aa50ccb0..81f458a699 100644 --- a/data/transactions/application.go +++ b/data/transactions/application.go @@ -210,7 +210,7 @@ func (rr ResourceRef) Empty() bool { // wellFormed checks that a ResourceRef is a proper member of `access. `rr` is // either empty a single kind of resource. Any internal indices point to proper // locations inside `access`. -func (rr ResourceRef) wellFormed(access []ResourceRef, proto config.ConsensusParams) error { +func (rr ResourceRef) wellFormed(access []ResourceRef, inCreate bool, proto config.ConsensusParams) error { // Count the number of non-empty fields count := 0 // The "basic" resources are inherently wellFormed @@ -231,7 +231,13 @@ func (rr ResourceRef) wellFormed(access []ResourceRef, proto config.ConsensusPar count++ } if !rr.Locals.Empty() { - if _, _, err := rr.Locals.Resolve(access, basics.Address{}); err != nil { + if !proto.AllowZeroLocalAppRef && rr.Locals.App == 0 { + return errors.New("0 App in LocalsRef is not supported") + } + if inCreate && rr.Locals.App == 0 { + return errors.New("0 App in LocalsRef during app create is not allowed or necessary") + } + if _, _, err := rr.Locals.Resolve(access, basics.Address{}, 0); err != nil { return err } count++ @@ -309,9 +315,9 @@ func (lr LocalsRef) Empty() bool { return lr == LocalsRef{} } -// Resolve looks up the referenced address and app in the access list. 0 is -// returned if the App index is 0, meaning "current app". -func (lr LocalsRef) Resolve(access []ResourceRef, sender basics.Address) (basics.Address, basics.AppIndex, error) { +// Resolve looks up the referenced address and app in the access list. Zero +// values are translated to the supplied sender or current app. +func (lr LocalsRef) Resolve(access []ResourceRef, sender basics.Address, current basics.AppIndex) (basics.Address, basics.AppIndex, error) { address := sender // Returned when lr.Address == 0 if lr.Address != 0 { if lr.Address > uint64(len(access)) { // recall that Access is 1-based @@ -322,12 +328,15 @@ func (lr LocalsRef) Resolve(access []ResourceRef, sender basics.Address) (basics return basics.Address{}, 0, fmt.Errorf("locals Address reference %d is not an Address", lr.Address) } } - if lr.App == 0 || lr.App > uint64(len(access)) { // 1-based - return basics.Address{}, 0, fmt.Errorf("locals App reference %d outside tx.Access", lr.App) - } - app := access[lr.App-1].App - if app == 0 { - return basics.Address{}, 0, fmt.Errorf("locals App reference %d is not an App", lr.App) + app := current // Returned when lr.App == 0 + if lr.App != 0 { + if lr.App > uint64(len(access)) { // 1-based + return basics.Address{}, 0, fmt.Errorf("locals App reference %d outside tx.Access", lr.App) + } + app = access[lr.App-1].App + if app == 0 { + return basics.Address{}, 0, fmt.Errorf("locals App reference %d is not an App", lr.App) + } } return address, app, nil } @@ -512,7 +521,7 @@ func (ac ApplicationCallTxnFields) wellFormed(proto config.ConsensusParams) erro } for _, rr := range ac.Access { - if err := rr.wellFormed(ac.Access, proto); err != nil { + if err := rr.wellFormed(ac.Access, ac.ApplicationID == 0, proto); err != nil { return err } } diff --git a/data/transactions/application_test.go b/data/transactions/application_test.go index 223c7a4ae8..6774875d9a 100644 --- a/data/transactions/application_test.go +++ b/data/transactions/application_test.go @@ -203,7 +203,8 @@ func TestAppCallAccessWellFormed(t *testing.T) { }, }, { - expectedError: "locals App reference 0 outside tx.Access", + // eliminate this test after AllowZeroAppInLocalsRef is removed + expectedError: "0 App in LocalsRef is not supported", ac: ApplicationCallTxnFields{ ApplicationID: 1, Access: []ResourceRef{ @@ -211,6 +212,28 @@ func TestAppCallAccessWellFormed(t *testing.T) { {Locals: LocalsRef{Address: 1}}, }, }, + cv: protocol.ConsensusV41, + }, + { + ac: ApplicationCallTxnFields{ + ApplicationID: 1, + Access: []ResourceRef{ + {Address: basics.Address{0xaa}}, + {Locals: LocalsRef{Address: 1}}, + }, + }, + }, + { + expectedError: "0 App in LocalsRef during app create is not allowed or necessary", + ac: ApplicationCallTxnFields{ + ApplicationID: 0, + ApprovalProgram: []byte{0x05}, + ClearStateProgram: []byte{0x05}, + Access: []ResourceRef{ + {Address: basics.Address{0xaa}}, + {Locals: LocalsRef{Address: 1}}, + }, + }, }, { ac: ApplicationCallTxnFields{ diff --git a/data/transactions/logic/box.go b/data/transactions/logic/box.go index a4e7845ff0..2799f98967 100644 --- a/data/transactions/logic/box.go +++ b/data/transactions/logic/box.go @@ -52,7 +52,7 @@ func (cx *EvalContext) availableBox(name string, operation BoxOperation, createS // we don't have to go to the disk. but we only allow one such access for // each spare (empty) box ref. that way, we can't end up needing to write // many separate newly created boxes. - if !ok && cx.Proto.EnableUnnamedBoxAccessInNewApps { + if !ok { if _, newAppAccess = cx.available.createdApps[cx.appID]; newAppAccess { if cx.available.unnamedAccess > 0 { ok = true // allow it diff --git a/data/transactions/logic/resources.go b/data/transactions/logic/resources.go index 39204daaf3..3384ef5527 100644 --- a/data/transactions/logic/resources.go +++ b/data/transactions/logic/resources.go @@ -252,11 +252,9 @@ func (cx *EvalContext) requireLocals(acct basics.Address, id basics.AppIndex) er } func (cx *EvalContext) allowsAssetTransfer(hdr *transactions.Header, tx *transactions.AssetTransferTxnFields) error { - // After EnableInnerClawbackWithoutSenderHolding appears in a consensus - // update, we should remove it from consensus params and assume it's true in - // the next release. It only needs to be in there so that it gates the - // behavior change in the release it first appears. - if !cx.Proto.EnableInnerClawbackWithoutSenderHolding || tx.AssetSender.IsZero() { + // When AssetSender is set (we're doing a clawback) we don't need the + // Sender's holding. The Sender/ClawbackAddress may not even have the asset. + if tx.AssetSender.IsZero() { err := cx.requireHolding(hdr.Sender, tx.XferAsset) if err != nil { return fmt.Errorf("axfer Sender: %w", err) @@ -323,17 +321,14 @@ func (r *resources) fillApplicationCallAccess(ep *EvalParams, hdr *transactions. r.shareHolding(address, asset) case !rr.Locals.Empty(): // ApplicationCallTxnFields.wellFormed ensures no error here. - address, app, _ := rr.Locals.Resolve(tx.Access, hdr.Sender) + address, app, _ := rr.Locals.Resolve(tx.Access, hdr.Sender, tx.ApplicationID) r.shareLocal(address, app) case !rr.Box.Empty(): // ApplicationCallTxnFields.wellFormed ensures no error here. app, name, _ := rr.Box.Resolve(tx.Access) r.shareBox(basics.BoxRef{App: app, Name: name}, tx.ApplicationID) default: - // all empty equals an "empty boxref" which allows one unnamed access - if ep.Proto.EnableUnnamedBoxAccessInNewApps { - r.unnamedAccess++ - } + r.unnamedAccess++ } } } @@ -374,7 +369,7 @@ func (r *resources) fillApplicationCallForeign(ep *EvalParams, hdr *transactions } for _, br := range tx.Boxes { - if ep.Proto.EnableUnnamedBoxAccessInNewApps && br.Empty() { + if br.Empty() { r.unnamedAccess++ } var app basics.AppIndex diff --git a/ledger/apptxn_test.go b/ledger/apptxn_test.go index b66eae15d9..f056ca71f7 100644 --- a/ledger/apptxn_test.go +++ b/ledger/apptxn_test.go @@ -3765,6 +3765,7 @@ func TestUnfundedSenders(t *testing.T) { // no min balance. func TestAppCallAppDuringInit(t *testing.T) { partitiontest.PartitionTest(t) + t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() ledgertesting.TestConsensusRange(t, 31, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { @@ -3810,3 +3811,168 @@ func TestAppCallAppDuringInit(t *testing.T) { dl.txn(&callInInit, problem) }) } + +// TestZeroAppLocalsAccess confirms that a 0, used as the app in a LocalRef indicates the current app. +func TestZeroAppLocalsAccess(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + genBalances, addrs, _ := ledgertesting.NewTestGenesis() + ledgertesting.TestConsensusRange(t, accessVersion, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) + defer dl.Close() + + // readFromArg0 tries to read the local named in Arg0 from the + // address given in Arg1. This allows testing of various ways to provide + // access to locals. + readFromArg0 := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApprovalProgram: main(` +txn ApplicationArgs 0 // An address +byte "XXX" +app_local_get +`), + } + + appID := dl.txn(&readFromArg0).ApplicationID + + dl.txn(&txntest.Txn{ + Type: "appl", + Sender: addrs[1], + ApplicationID: appID, + ApplicationArgs: [][]byte{addrs[2][:]}, + }, "unavailable Account "+addrs[2].String()) + + // The old way: foreign account, with the implied access the cross product with current app + dl.txn(&txntest.Txn{ + Type: "appl", + Sender: addrs[1], + ApplicationID: appID, + ApplicationArgs: [][]byte{addrs[2][:]}, + Accounts: []basics.Address{addrs[2]}, + }, addrs[2].String()+" has not opted in") // shows that it's available now + + // The long way with Access: specify the called app as a resource ref, + // then have the localref point to it. + dl.txn(&txntest.Txn{ + Type: "appl", + Sender: addrs[1], // opt-in + ApplicationID: appID, + ApplicationArgs: [][]byte{addrs[2][:]}, + Access: []transactions.ResourceRef{{ + Address: addrs[2], + }, { + App: appID, + }, { + Locals: transactions.LocalsRef{ + Address: 1, App: 2, + }, + }}, + }, addrs[2].String()+" has not opted in") // shows that it's available now + + // The short way with Access: use 0 to mean the called app + problem := "0 App in LocalsRef is not supported" + if ver >= 42 { // 0 app is allowed now. (Remove this after consensus change) + problem = addrs[2].String() + " has not opted in" + } + + dl.txn(&txntest.Txn{ + Type: "appl", + Sender: addrs[1], // opt-in + ApplicationID: appID, + ApplicationArgs: [][]byte{addrs[2][:]}, + Access: []transactions.ResourceRef{{ + Address: addrs[2], + }, { + Locals: transactions.LocalsRef{ + Address: 1, App: 0, + }, + }}, + }, problem) + + }) + +} + +// TestLocalAccessInNewApps shows that in a group that creates a new app, that +// app can access any available account's locals for that app. Of course, +// although the locals may be available, it's quite hard to construct examples +// in which an account is opted-in to the new app. +func TestLocalAccessInNewApps(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + genBalances, addrs, _ := ledgertesting.NewTestGenesis() + ledgertesting.TestConsensusRange(t, accessVersion, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) { + dl := NewDoubleLedger(t, genBalances, cv, cfg) + defer dl.Close() + + // callArg0WithArg1 is an app that simply calls the appID provided in + // the 0th argument. It supplies its own 1st arg as the callee's + // 0th. It is intended to be called by a top-level app in a group where + // a previous transactions has created a new app. The caller of this + // app determines the id of that newly created app and provides it to + // `readFromArg0`. The goal is to re-invoke the newly created app and + // show it can access locals for an available account even though the + // Locals do not explcitly appear in an access list. + + callArg0WithArg1 := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApprovalProgram: main(` +itxn_begin +int appl; itxn_field TypeEnum +txn ApplicationArgs 0; btoi; itxn_field ApplicationID +txn ApplicationArgs 1; itxn_field ApplicationArgs +itxn_submit +int 1 +`), + } + + callID := dl.txn(&callArg0WithArg1).ApplicationID + + // readFromArg0 tries to read the local named in Arg0 from the + // address given in Arg1. This allows testing of various ways to provide + // access to locals. + readFromArg0 := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApprovalProgram: main(` +txn ApplicationArgs 0 // An address +byte "XXX" +app_local_get +`), + } + + callFirst := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApprovalProgram: ` // Don't use main(), so this runs at creation time +itxn_begin +int appl; itxn_field TypeEnum +gtxn 0 CreatedApplicationID // get the app ID created in first txn +itob +itxn_field ApplicationArgs +txn ApplicationArgs 0; itxn_field ApplicationArgs +int ` + strconv.FormatUint(uint64(callID), 10) + ` +itxn_field ApplicationID +itxn_submit +`, + Access: []transactions.ResourceRef{{ + App: callID, + }}, + Fee: 1_000_000, // Oversize fee, so that newly created app can make inner call + } + + dl.txgroup("unavailable Account "+addrs[2].String(), &readFromArg0, callFirst.Args(string(addrs[2][:]))) + + callWithAcctAccess := callFirst + callWithAcctAccess.Access = append(callWithAcctAccess.Access, transactions.ResourceRef{Address: addrs[2]}) + // The "not opted in" error shows that the local ref has become + // available because addr[2] because available, this demonstrates that + // the LocalRef is not needed because the app in question was made + // earlier in the same group. + dl.txgroup(addrs[2].String()+" has not opted in", &readFromArg0, callWithAcctAccess.Args(string(addrs[2][:]))) + }) +} diff --git a/ledger/boxtxn_test.go b/ledger/boxtxn_test.go index a4f5b1c5d0..e48082a332 100644 --- a/ledger/boxtxn_test.go +++ b/ledger/boxtxn_test.go @@ -130,9 +130,8 @@ var passThruSource = main(` const ( boxVersion = 36 - accessVersion = 38 + accessVersion = 41 boxQuotaBumpVersion = 41 - newAppCreateVersion = 41 ) func boxFee(p config.ConsensusParams, nameAndValueSize uint64) uint64 { @@ -737,7 +736,7 @@ var passThruCreator = main(` itxn_submit `) -// TestNewAppBoxCreate exercised proto.EnableUnnamedBoxCreate +// TestNewAppBoxCreate exercises the creation of boxes in newly created apps func TestNewAppBoxCreate(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() @@ -775,7 +774,7 @@ func testNewAppBoxCreate(t *testing.T, requestedTealVersion int) { // 2) a) Use the predicted appID to name the box ref. // or b) Use 0 as the app in the box ref, meaning "this app" - // or c) EnableUnnamedBoxCreate will allow such a creation if there are empty box refs. + // or c) v41 (accessVersion) will allow it if there are empty box refs. // 2a is pretty much impossible in practice, we can only do it here // because our blockchain is "quiet" we know the upcoming appID. @@ -864,52 +863,46 @@ func testNewAppBoxCreate(t *testing.T, requestedTealVersion int) { {Index: 0, Name: []byte{0x02}}, }}) - if ver >= newAppCreateVersion { - // 2c. Create it with an empty box ref - dl.txn(&txntest.Txn{Type: "appl", Sender: addrs[0], - ApprovalProgram: createSrcVer, ApplicationArgs: [][]byte{{0x01}}, - Boxes: []transactions.BoxRef{{}}}) + // 2c. Create it with an empty box ref + dl.txn(&txntest.Txn{Type: "appl", Sender: addrs[0], + ApprovalProgram: createSrcVer, ApplicationArgs: [][]byte{{0x01}}, + Boxes: []transactions.BoxRef{{}}}) + if ver >= accessVersion { // 2c. Create it with an empty box ref dl.txn(&txntest.Txn{Type: "appl", Sender: addrs[0], ApprovalProgram: createSrcVer, ApplicationArgs: [][]byte{{0x01}}, Access: []transactions.ResourceRef{{Box: transactions.BoxRef{}}}}) + } - // but you can't do a second create - dl.txn(&txntest.Txn{Type: "appl", Sender: addrs[0], - ApprovalProgram: doubleSrc, ApplicationArgs: [][]byte{{0x01}, {0x02}}, - Boxes: []transactions.BoxRef{{}}}, - "invalid Box reference 0x02") + // but you can't do a second create + dl.txn(&txntest.Txn{Type: "appl", Sender: addrs[0], + ApprovalProgram: doubleSrc, ApplicationArgs: [][]byte{{0x01}, {0x02}}, + Boxes: []transactions.BoxRef{{}}}, + "invalid Box reference 0x02") - // until you add a second box ref - dl.txn(&txntest.Txn{Type: "appl", Sender: addrs[0], - ApprovalProgram: doubleSrc, ApplicationArgs: [][]byte{{0x01}, {0x02}}, - Boxes: []transactions.BoxRef{{}, {}}}) - - // Now confirm that 2c also works for an inner created app - ops, err := logic.AssembleString("#pragma version 12\n" + createSrc) - require.NoError(t, err, ops.Errors) - createSrcByteCode := ops.Program - // create app as an inner, fails w/o empty box ref - dl.txn(&txntest.Txn{Sender: addrs[0], - Type: "appl", - ApplicationID: passID, - ApplicationArgs: [][]byte{createSrcByteCode, {0x01}}, - }, "invalid Box reference 0x01") - // create app as an inner, succeeds w/ empty box ref - dl.txn(&txntest.Txn{Sender: addrs[0], - Type: "appl", - ApplicationID: passID, - ApplicationArgs: [][]byte{createSrcByteCode, {0x01}}, - Boxes: []transactions.BoxRef{{}}, - }) - } else { - // 2c. Doesn't work yet until `newAppCreateVersion` - dl.txn(&txntest.Txn{Type: "appl", Sender: addrs[0], - ApprovalProgram: createSrcVer, ApplicationArgs: [][]byte{{0x01}}, - Boxes: []transactions.BoxRef{{}}}, - "invalid Box reference 0x01") - } + // until you add a second box ref + dl.txn(&txntest.Txn{Type: "appl", Sender: addrs[0], + ApprovalProgram: doubleSrc, ApplicationArgs: [][]byte{{0x01}, {0x02}}, + Boxes: []transactions.BoxRef{{}, {}}}) + + // Now confirm that 2c also works for an inner created app + ops, err := logic.AssembleString("#pragma version 8\n" + createSrc) + require.NoError(t, err, ops.Errors) + createSrcByteCode := ops.Program + // create app as an inner, fails w/o empty box ref + dl.txn(&txntest.Txn{Sender: addrs[0], + Type: "appl", + ApplicationID: passID, + ApplicationArgs: [][]byte{createSrcByteCode, {0x01}}, + }, "invalid Box reference 0x01") + // create app as an inner, succeeds w/ empty box ref + dl.txn(&txntest.Txn{Sender: addrs[0], + Type: "appl", + ApplicationID: passID, + ApplicationArgs: [][]byte{createSrcByteCode, {0x01}}, + Boxes: []transactions.BoxRef{{}}, + }) } }) }