Skip to content

Commit 38589bf

Browse files
committed
AVM: Allow 0 as app in LocalRef, specifying current app
A localref inside of tx.Access ought to be able to use 0 for "current app". It was an oversight to prohibit it. This PR allows it, but is consensus gated. This was a slightly bigger that chnage than expected (because the access list must now be scanned at app create time for localrefs woth 0 apps, like boxes were). So a few tests are needed before this is reviewable. Also removes two consensus flags that can be assumed true now because they are strictly more lenient.
1 parent da20eba commit 38589bf

File tree

9 files changed

+272
-100
lines changed

9 files changed

+272
-100
lines changed

config/consensus.go

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -539,11 +539,6 @@ type ConsensusParams struct {
539539
// EnableBoxRefNameError specifies that box ref names should be validated early
540540
EnableBoxRefNameError bool
541541

542-
// EnableUnnamedBoxAccessInNewApps allows newly created (in this group) apps to
543-
// create boxes that were not named in a box ref. Each empty box ref in the
544-
// group allows one such creation.
545-
EnableUnnamedBoxAccessInNewApps bool
546-
547542
// ExcludeExpiredCirculation excludes expired stake from the total online stake
548543
// used by agreement for Circulation, and updates the calculation of StateProofOnlineTotalWeight used
549544
// by state proofs to use the same method (rather than excluding stake from the top N stakeholders as before).
@@ -572,11 +567,10 @@ type ConsensusParams struct {
572567
// EnableSha512BlockHash adds an additional SHA-512 hash to the block header.
573568
EnableSha512BlockHash bool
574569

575-
// EnableInnerClawbackWithoutSenderHolding allows an inner clawback (axfer
576-
// w/ AssetSender) even if the Sender holding of the asset is not
577-
// available. This parameters can be removed and assumed true after the
578-
// first consensus release in which it is set true.
579-
EnableInnerClawbackWithoutSenderHolding bool
570+
// AllowZeroLocalAppRef allows for a 0 in a LocalRef of the access list to
571+
// specify the current app. This parameter can be removed and assumed true
572+
// after the first consensus release in which it is set true.
573+
AllowZeroLocalAppRef bool
580574
}
581575

582576
// ProposerPayoutRules puts several related consensus parameters in one place. The same
@@ -1438,13 +1432,10 @@ func initConsensusProtocols() {
14381432
v41.EnableAppVersioning = true
14391433
v41.EnableSha512BlockHash = true
14401434

1441-
v41.EnableUnnamedBoxAccessInNewApps = true
1442-
14431435
// txn.Access work
14441436
v41.MaxAppTxnAccounts = 8 // Accounts are no worse than others, they should be the same
14451437
v41.MaxAppAccess = 16 // Twice as many, though cross products are explicit
14461438
v41.BytesPerBoxReference = 2048 // Count is more important that bytes, loosen up
1447-
v41.EnableInnerClawbackWithoutSenderHolding = true
14481439
v41.LogicSigMsig = false
14491440
v41.LogicSigLMsig = true
14501441

@@ -1462,6 +1453,8 @@ func initConsensusProtocols() {
14621453

14631454
vFuture.LogicSigVersion = 13 // When moving this to a release, put a new higher LogicSigVersion here
14641455

1456+
vFuture.AllowZeroLocalAppRef = true
1457+
14651458
Consensus[protocol.ConsensusFuture] = vFuture
14661459

14671460
// vAlphaX versions are an separate series of consensus parameters and versions for alphanet

daemon/algod/api/server/v2/handlers.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1318,7 +1318,7 @@ func (v2 *Handlers) SimulateTransaction(ctx echo.Context, params model.SimulateT
13181318
}
13191319
}
13201320

1321-
response := convertSimulationResult(simulationResult, proto.EnableUnnamedBoxAccessInNewApps)
1321+
response := convertSimulationResult(simulationResult)
13221322

13231323
handle, contentType, err := getCodecHandle((*string)(params.Format))
13241324
if err != nil {

daemon/algod/api/server/v2/utils.go

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -462,13 +462,13 @@ func convertTxnTrace(txnTrace *simulation.TransactionTrace) *model.SimulationTra
462462
}
463463
}
464464

465-
func convertTxnResult(txnResult simulation.TxnResult, simplify bool) PreEncodedSimulateTxnResult {
465+
func convertTxnResult(txnResult simulation.TxnResult) PreEncodedSimulateTxnResult {
466466
result := PreEncodedSimulateTxnResult{
467467
Txn: ConvertInnerTxn(&txnResult.Txn),
468468
AppBudgetConsumed: omitEmpty(txnResult.AppBudgetConsumed),
469469
LogicSigBudgetConsumed: omitEmpty(txnResult.LogicSigBudgetConsumed),
470470
TransactionTrace: convertTxnTrace(txnResult.Trace),
471-
UnnamedResourcesAccessed: convertUnnamedResourcesAccessed(txnResult.UnnamedResourcesAccessed, simplify),
471+
UnnamedResourcesAccessed: convertUnnamedResourcesAccessed(txnResult.UnnamedResourcesAccessed),
472472
}
473473

474474
if !txnResult.FixedSigner.IsZero() {
@@ -479,13 +479,11 @@ func convertTxnResult(txnResult simulation.TxnResult, simplify bool) PreEncodedS
479479
return result
480480
}
481481

482-
func convertUnnamedResourcesAccessed(resources *simulation.ResourceTracker, simplify bool) *model.SimulateUnnamedResourcesAccessed {
482+
func convertUnnamedResourcesAccessed(resources *simulation.ResourceTracker) *model.SimulateUnnamedResourcesAccessed {
483483
if resources == nil {
484484
return nil
485485
}
486-
if simplify {
487-
resources.Simplify()
488-
}
486+
resources.Simplify()
489487
return &model.SimulateUnnamedResourcesAccessed{
490488
Accounts: sliceOrNil(stringSlice(slices.Collect(maps.Keys(resources.Accounts)))),
491489
Assets: sliceOrNil(slices.Collect(maps.Keys(resources.Assets))),
@@ -557,18 +555,15 @@ func convertSimulateInitialStates(initialStates *simulation.ResourcesInitialStat
557555
}
558556
}
559557

560-
func convertTxnGroupResult(txnGroupResult simulation.TxnGroupResult, simplify bool) PreEncodedSimulateTxnGroupResult {
561-
txnResults := make([]PreEncodedSimulateTxnResult, len(txnGroupResult.Txns))
562-
for i, txnResult := range txnGroupResult.Txns {
563-
txnResults[i] = convertTxnResult(txnResult, simplify)
564-
}
558+
func convertTxnGroupResult(txnGroupResult simulation.TxnGroupResult) PreEncodedSimulateTxnGroupResult {
559+
txnResults := util.Map(txnGroupResult.Txns, convertTxnResult)
565560

566561
encoded := PreEncodedSimulateTxnGroupResult{
567562
Txns: txnResults,
568563
FailureMessage: omitEmpty(txnGroupResult.FailureMessage),
569564
AppBudgetAdded: omitEmpty(txnGroupResult.AppBudgetAdded),
570565
AppBudgetConsumed: omitEmpty(txnGroupResult.AppBudgetConsumed),
571-
UnnamedResourcesAccessed: convertUnnamedResourcesAccessed(txnGroupResult.UnnamedResourcesAccessed, simplify),
566+
UnnamedResourcesAccessed: convertUnnamedResourcesAccessed(txnGroupResult.UnnamedResourcesAccessed),
572567
}
573568

574569
if len(txnGroupResult.FailedAt) > 0 {
@@ -579,7 +574,7 @@ func convertTxnGroupResult(txnGroupResult simulation.TxnGroupResult, simplify bo
579574
return encoded
580575
}
581576

582-
func convertSimulationResult(result simulation.Result, simplify bool) PreEncodedSimulateResponse {
577+
func convertSimulationResult(result simulation.Result) PreEncodedSimulateResponse {
583578
var evalOverrides *model.SimulationEvalOverrides
584579
if result.EvalOverrides != (simulation.ResultEvalOverrides{}) {
585580
evalOverrides = &model.SimulationEvalOverrides{
@@ -593,11 +588,9 @@ func convertSimulationResult(result simulation.Result, simplify bool) PreEncoded
593588
}
594589

595590
return PreEncodedSimulateResponse{
596-
Version: result.Version,
597-
LastRound: result.LastRound,
598-
TxnGroups: util.Map(result.TxnGroups, func(tg simulation.TxnGroupResult) PreEncodedSimulateTxnGroupResult {
599-
return convertTxnGroupResult(tg, simplify)
600-
}),
591+
Version: result.Version,
592+
LastRound: result.LastRound,
593+
TxnGroups: util.Map(result.TxnGroups, convertTxnGroupResult),
601594
EvalOverrides: evalOverrides,
602595
ExecTraceConfig: result.TraceConfig,
603596
InitialStates: convertSimulateInitialStates(result.InitialStates),

data/transactions/application.go

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ func (rr ResourceRef) Empty() bool {
210210
// wellFormed checks that a ResourceRef is a proper member of `access. `rr` is
211211
// either empty a single kind of resource. Any internal indices point to proper
212212
// locations inside `access`.
213-
func (rr ResourceRef) wellFormed(access []ResourceRef, proto config.ConsensusParams) error {
213+
func (rr ResourceRef) wellFormed(access []ResourceRef, inCreate bool, proto config.ConsensusParams) error {
214214
// Count the number of non-empty fields
215215
count := 0
216216
// The "basic" resources are inherently wellFormed
@@ -231,7 +231,13 @@ func (rr ResourceRef) wellFormed(access []ResourceRef, proto config.ConsensusPar
231231
count++
232232
}
233233
if !rr.Locals.Empty() {
234-
if _, _, err := rr.Locals.Resolve(access, basics.Address{}); err != nil {
234+
if !proto.AllowZeroLocalAppRef && rr.Locals.App == 0 {
235+
return errors.New("0 App in LocalsRef is not supported")
236+
}
237+
if inCreate && rr.Locals.App == 0 {
238+
return errors.New("0 App in LocalsRef during app create is not allowed or necessary")
239+
}
240+
if _, _, err := rr.Locals.Resolve(access, basics.Address{}, 0); err != nil {
235241
return err
236242
}
237243
count++
@@ -309,9 +315,9 @@ func (lr LocalsRef) Empty() bool {
309315
return lr == LocalsRef{}
310316
}
311317

312-
// Resolve looks up the referenced address and app in the access list. 0 is
313-
// returned if the App index is 0, meaning "current app".
314-
func (lr LocalsRef) Resolve(access []ResourceRef, sender basics.Address) (basics.Address, basics.AppIndex, error) {
318+
// Resolve looks up the referenced address and app in the access list. Zero
319+
// values are translated to the supplied sender or current app.
320+
func (lr LocalsRef) Resolve(access []ResourceRef, sender basics.Address, current basics.AppIndex) (basics.Address, basics.AppIndex, error) {
315321
address := sender // Returned when lr.Address == 0
316322
if lr.Address != 0 {
317323
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
322328
return basics.Address{}, 0, fmt.Errorf("locals Address reference %d is not an Address", lr.Address)
323329
}
324330
}
325-
if lr.App == 0 || lr.App > uint64(len(access)) { // 1-based
326-
return basics.Address{}, 0, fmt.Errorf("locals App reference %d outside tx.Access", lr.App)
327-
}
328-
app := access[lr.App-1].App
329-
if app == 0 {
330-
return basics.Address{}, 0, fmt.Errorf("locals App reference %d is not an App", lr.App)
331+
app := current // Returned when lr.App == 0
332+
if lr.App != 0 {
333+
if lr.App > uint64(len(access)) { // 1-based
334+
return basics.Address{}, 0, fmt.Errorf("locals App reference %d outside tx.Access", lr.App)
335+
}
336+
app = access[lr.App-1].App
337+
if app == 0 {
338+
return basics.Address{}, 0, fmt.Errorf("locals App reference %d is not an App", lr.App)
339+
}
331340
}
332341
return address, app, nil
333342
}
@@ -499,7 +508,7 @@ func (ac ApplicationCallTxnFields) wellFormed(proto config.ConsensusParams) erro
499508
}
500509

501510
for _, rr := range ac.Access {
502-
if err := rr.wellFormed(ac.Access, proto); err != nil {
511+
if err := rr.wellFormed(ac.Access, ac.ApplicationID == 0, proto); err != nil {
503512
return err
504513
}
505514
}

data/transactions/application_test.go

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,14 +205,37 @@ func TestAppCallAccessWellFormed(t *testing.T) {
205205
},
206206
},
207207
{
208-
expectedError: "locals App reference 0 outside tx.Access",
208+
// eliminate this test after AllowZeroAppInLocalsRef is removed
209+
expectedError: "0 App in LocalsRef is not supported",
209210
ac: ApplicationCallTxnFields{
210211
ApplicationID: 1,
211212
Access: []ResourceRef{
212213
{Address: basics.Address{0xaa}},
213214
{Locals: LocalsRef{Address: 1}},
214215
},
215216
},
217+
cv: protocol.ConsensusV41,
218+
},
219+
{
220+
ac: ApplicationCallTxnFields{
221+
ApplicationID: 1,
222+
Access: []ResourceRef{
223+
{Address: basics.Address{0xaa}},
224+
{Locals: LocalsRef{Address: 1}},
225+
},
226+
},
227+
},
228+
{
229+
expectedError: "0 App in LocalsRef during app create is not allowed or necessary",
230+
ac: ApplicationCallTxnFields{
231+
ApplicationID: 0,
232+
ApprovalProgram: []byte{0x05},
233+
ClearStateProgram: []byte{0x05},
234+
Access: []ResourceRef{
235+
{Address: basics.Address{0xaa}},
236+
{Locals: LocalsRef{Address: 1}},
237+
},
238+
},
216239
},
217240
{
218241
ac: ApplicationCallTxnFields{

data/transactions/logic/box.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ func (cx *EvalContext) availableBox(name string, operation BoxOperation, createS
5252
// we don't have to go to the disk. but we only allow one such access for
5353
// each spare (empty) box ref. that way, we can't end up needing to write
5454
// many separate newly created boxes.
55-
if !ok && cx.Proto.EnableUnnamedBoxAccessInNewApps {
55+
if !ok {
5656
if _, newAppAccess = cx.available.createdApps[cx.appID]; newAppAccess {
5757
if cx.available.unnamedAccess > 0 {
5858
ok = true // allow it

data/transactions/logic/resources.go

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -252,11 +252,9 @@ func (cx *EvalContext) requireLocals(acct basics.Address, id basics.AppIndex) er
252252
}
253253

254254
func (cx *EvalContext) allowsAssetTransfer(hdr *transactions.Header, tx *transactions.AssetTransferTxnFields) error {
255-
// After EnableInnerClawbackWithoutSenderHolding appears in a consensus
256-
// update, we should remove it from consensus params and assume it's true in
257-
// the next release. It only needs to be in there so that it gates the
258-
// behavior change in the release it first appears.
259-
if !cx.Proto.EnableInnerClawbackWithoutSenderHolding || tx.AssetSender.IsZero() {
255+
// When AssetSender is set (we're doing a clawback) we don't need the
256+
// Sender's holding. The Sender/ClawbackAddress may not even have the asset.
257+
if tx.AssetSender.IsZero() {
260258
err := cx.requireHolding(hdr.Sender, tx.XferAsset)
261259
if err != nil {
262260
return fmt.Errorf("axfer Sender: %w", err)
@@ -323,17 +321,14 @@ func (r *resources) fillApplicationCallAccess(ep *EvalParams, hdr *transactions.
323321
r.shareHolding(address, asset)
324322
case !rr.Locals.Empty():
325323
// ApplicationCallTxnFields.wellFormed ensures no error here.
326-
address, app, _ := rr.Locals.Resolve(tx.Access, hdr.Sender)
324+
address, app, _ := rr.Locals.Resolve(tx.Access, hdr.Sender, tx.ApplicationID)
327325
r.shareLocal(address, app)
328326
case !rr.Box.Empty():
329327
// ApplicationCallTxnFields.wellFormed ensures no error here.
330328
app, name, _ := rr.Box.Resolve(tx.Access)
331329
r.shareBox(basics.BoxRef{App: app, Name: name}, tx.ApplicationID)
332330
default:
333-
// all empty equals an "empty boxref" which allows one unnamed access
334-
if ep.Proto.EnableUnnamedBoxAccessInNewApps {
335-
r.unnamedAccess++
336-
}
331+
r.unnamedAccess++
337332
}
338333
}
339334
}
@@ -374,7 +369,7 @@ func (r *resources) fillApplicationCallForeign(ep *EvalParams, hdr *transactions
374369
}
375370

376371
for _, br := range tx.Boxes {
377-
if ep.Proto.EnableUnnamedBoxAccessInNewApps && br.Empty() {
372+
if br.Empty() {
378373
r.unnamedAccess++
379374
}
380375
var app basics.AppIndex

0 commit comments

Comments
 (0)