From 1d275d4c9ff2bdd54eb18243070347111dd49407 Mon Sep 17 00:00:00 2001 From: Joe Polny Date: Fri, 23 Feb 2024 17:34:33 -0500 Subject: [PATCH 1/7] get extra opcode budget from FeeCredit --- data/transactions/logic/eval.go | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 210d7a3164..de997afb9d 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -1541,9 +1541,29 @@ func (cx *EvalContext) step() error { } } + // TODO: Right now opcode budget is uncapped with this mechanism, which we probably don't want + // We need to do some sort of accouting in/with remainingInners() to ensure we stay below the max if opcost > cx.remainingBudget() { - return fmt.Errorf("pc=%3d dynamic cost budget exceeded, executing %s: local program cost was %d", - cx.pc, spec.Name, cx.cost) + requiredExtraBudget := uint64(opcost - cx.remainingBudget()) + var budgetPerMinFee uint64 + + if cx.runMode == ModeSig { + budgetPerMinFee = cx.Proto.LogicSigMaxCost + } else { + budgetPerMinFee = uint64(cx.Proto.MaxAppProgramCost) + } + + shortfall := cx.Proto.MinTxnFee * (requiredExtraBudget / budgetPerMinFee) + + if requiredExtraBudget%budgetPerMinFee != 0 { + shortfall += cx.Proto.MinTxnFee + } + + if cx.FeeCredit == nil || *cx.FeeCredit < shortfall { + return fmt.Errorf("pc=%3d dynamic cost budget exceeded, executing %s: local program cost was %d", + cx.pc, spec.Name, cx.cost) + } + *cx.FeeCredit -= shortfall } cx.cost += opcost From f9f040135e15653449fca85199a87f7775da0f25 Mon Sep 17 00:00:00 2001 From: Joe Polny Date: Fri, 23 Feb 2024 18:06:17 -0500 Subject: [PATCH 2/7] simplify shortfall --- data/transactions/logic/eval.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index de997afb9d..723ba53dcf 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -1553,11 +1553,7 @@ func (cx *EvalContext) step() error { budgetPerMinFee = uint64(cx.Proto.MaxAppProgramCost) } - shortfall := cx.Proto.MinTxnFee * (requiredExtraBudget / budgetPerMinFee) - - if requiredExtraBudget%budgetPerMinFee != 0 { - shortfall += cx.Proto.MinTxnFee - } + shortfall := cx.Proto.MinTxnFee * ((requiredExtraBudget + budgetPerMinFee - 1) / budgetPerMinFee) if cx.FeeCredit == nil || *cx.FeeCredit < shortfall { return fmt.Errorf("pc=%3d dynamic cost budget exceeded, executing %s: local program cost was %d", From f7c90dd02c4e67fff1e840573bf0ae0d397c3f00 Mon Sep 17 00:00:00 2001 From: Joe Polny Date: Fri, 23 Feb 2024 18:14:16 -0500 Subject: [PATCH 3/7] increase budget --- data/transactions/logic/eval.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 723ba53dcf..031f4469ec 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -1553,13 +1553,21 @@ func (cx *EvalContext) step() error { budgetPerMinFee = uint64(cx.Proto.MaxAppProgramCost) } - shortfall := cx.Proto.MinTxnFee * ((requiredExtraBudget + budgetPerMinFee - 1) / budgetPerMinFee) + requiredMinFees := (requiredExtraBudget + budgetPerMinFee - 1) / budgetPerMinFee + shortfall := cx.Proto.MinTxnFee * requiredMinFees if cx.FeeCredit == nil || *cx.FeeCredit < shortfall { return fmt.Errorf("pc=%3d dynamic cost budget exceeded, executing %s: local program cost was %d", cx.pc, spec.Name, cx.cost) } *cx.FeeCredit -= shortfall + + switch { + case cx.PooledApplicationBudget != nil: + *cx.PooledApplicationBudget += int(budgetPerMinFee * requiredMinFees) + case cx.PooledLogicSigBudget != nil: + *cx.PooledLogicSigBudget += int(budgetPerMinFee * requiredMinFees) + } } cx.cost += opcost From 34063cf9c7dd7a5c957a4f5c335c84ac5b8370bb Mon Sep 17 00:00:00 2001 From: Joe Polny Date: Fri, 23 Feb 2024 19:31:14 -0500 Subject: [PATCH 4/7] max budget check --- data/transactions/logic/eval.go | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 031f4469ec..3554693dcb 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -1541,16 +1541,16 @@ func (cx *EvalContext) step() error { } } - // TODO: Right now opcode budget is uncapped with this mechanism, which we probably don't want - // We need to do some sort of accouting in/with remainingInners() to ensure we stay below the max if opcost > cx.remainingBudget() { requiredExtraBudget := uint64(opcost - cx.remainingBudget()) var budgetPerMinFee uint64 if cx.runMode == ModeSig { budgetPerMinFee = cx.Proto.LogicSigMaxCost - } else { + } else if cx.runMode == ModeApp { budgetPerMinFee = uint64(cx.Proto.MaxAppProgramCost) + } else { + return fmt.Errorf("unknown run mode %d", cx.runMode) } requiredMinFees := (requiredExtraBudget + budgetPerMinFee - 1) / budgetPerMinFee @@ -1562,11 +1562,19 @@ func (cx *EvalContext) step() error { } *cx.FeeCredit -= shortfall - switch { - case cx.PooledApplicationBudget != nil: + if cx.runMode == ModeApp { + *cx.pooledAllowedInners -= int(requiredMinFees) + if *cx.pooledAllowedInners < 0 { + return fmt.Errorf("pc=%3d dynamic group cost budget exceeded, executing %s: local program cost was %d.", + cx.pc, spec.Name, cx.cost) + } + *cx.PooledApplicationBudget += int(budgetPerMinFee * requiredMinFees) - case cx.PooledLogicSigBudget != nil: + } else if cx.runMode == ModeSig { + // TODO: return error if we've used MaxTxGroupSize worth of lsig budget in the group *cx.PooledLogicSigBudget += int(budgetPerMinFee * requiredMinFees) + } else { + return fmt.Errorf("unknown run mode %d", cx.runMode) } } From 71181edcf9117c0238991f0532ba12f2b050cd94 Mon Sep 17 00:00:00 2001 From: Joe Polny Date: Sat, 24 Feb 2024 09:33:04 -0500 Subject: [PATCH 5/7] TestInnerBudgetIncrementFromFee --- data/transactions/logic/evalAppTxn_test.go | 46 ++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/data/transactions/logic/evalAppTxn_test.go b/data/transactions/logic/evalAppTxn_test.go index 9a6d0ebc41..9ed76985b7 100644 --- a/data/transactions/logic/evalAppTxn_test.go +++ b/data/transactions/logic/evalAppTxn_test.go @@ -1500,6 +1500,52 @@ itxn_submit; TestApp(t, buy+buy+strings.Repeat(waste, 12)+"int 1", ep) } +func TestInnerBudgetIncrementFromFee(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + ep, tx, ledger := MakeSampleEnv() + + waste := `global CurrentApplicationAddress; keccak256; pop;` + + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + ledger.NewAccount(appAddr(888), 50_000) + tx.ForeignApps = []basics.AppIndex{basics.AppIndex(222)} + + // For every variation ensure the following: + // + // 1. The opcode budget is increased, but not more than necessary + // 2. The fee credit is deducted + + *ep.FeeCredit = 0 + TestApp(t, strings.Repeat(waste, 5)+"int 1", ep) + require.IsIncreasing(t, []int{0, *ep.PooledApplicationBudget, ep.Proto.MaxAppProgramCost}) + require.Equal(t, uint64(0), *ep.FeeCredit) + TestApp(t, strings.Repeat(waste, 6)+"int 1", ep, "dynamic cost budget exceeded") + + *ep.FeeCredit = ep.Proto.MinTxnFee + TestApp(t, strings.Repeat(waste, 6)+"int 1", ep) + require.IsIncreasing(t, []int{0, *ep.PooledApplicationBudget, ep.Proto.MaxAppProgramCost}) + require.Equal(t, uint64(0), *ep.FeeCredit) + TestApp(t, strings.Repeat(waste, 11)+"int 1", ep, "dynamic cost budget exceeded") + + *ep.FeeCredit = ep.Proto.MinTxnFee * 2 + TestApp(t, strings.Repeat(waste, 11)+"int 1", ep) + require.IsIncreasing(t, []int{0, *ep.PooledApplicationBudget, ep.Proto.MaxAppProgramCost}) + require.Equal(t, uint64(0), *ep.FeeCredit) + TestApp(t, strings.Repeat(waste, 16)+"int 1", ep, "dynamic cost budget exceeded") + + // Ensure that the fee credit isn't deducted more than necessary + *ep.FeeCredit = ep.Proto.MinTxnFee * 3 + TestApp(t, strings.Repeat(waste, 11)+"int 1", ep) + require.IsIncreasing(t, []int{0, *ep.PooledApplicationBudget, ep.Proto.MaxAppProgramCost}) + require.Equal(t, ep.Proto.MinTxnFee, *ep.FeeCredit) + + // Ensure the group budget is still enforced + *ep.FeeCredit = ep.Proto.MinTxnFee * 257 + TestApp(t, strings.Repeat(waste, 5*257)+"int 1", ep, "dynamic group cost budget exceeded") +} + func TestIncrementCheck(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() From b6635a629aa25d47fb90f037c54ea13b27dcb8e7 Mon Sep 17 00:00:00 2001 From: Joe Polny Date: Sat, 24 Feb 2024 09:58:00 -0500 Subject: [PATCH 6/7] remove . from error --- data/transactions/logic/eval.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 3554693dcb..962f26b505 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -1565,7 +1565,7 @@ func (cx *EvalContext) step() error { if cx.runMode == ModeApp { *cx.pooledAllowedInners -= int(requiredMinFees) if *cx.pooledAllowedInners < 0 { - return fmt.Errorf("pc=%3d dynamic group cost budget exceeded, executing %s: local program cost was %d.", + return fmt.Errorf("pc=%3d dynamic group cost budget exceeded, executing %s: local program cost was %d", cx.pc, spec.Name, cx.cost) } From 54914c54af8129e32157a690241cb61be3cd801f Mon Sep 17 00:00:00 2001 From: Joe Polny Date: Fri, 3 May 2024 08:29:16 -0400 Subject: [PATCH 7/7] only allow fees for app budget --- data/transactions/logic/eval.go | 39 ++++++++++++--------------------- 1 file changed, 14 insertions(+), 25 deletions(-) diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 962f26b505..8dcfdcfa04 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -1542,40 +1542,29 @@ func (cx *EvalContext) step() error { } if opcost > cx.remainingBudget() { - requiredExtraBudget := uint64(opcost - cx.remainingBudget()) - var budgetPerMinFee uint64 - - if cx.runMode == ModeSig { - budgetPerMinFee = cx.Proto.LogicSigMaxCost - } else if cx.runMode == ModeApp { - budgetPerMinFee = uint64(cx.Proto.MaxAppProgramCost) - } else { - return fmt.Errorf("unknown run mode %d", cx.runMode) + if cx.runMode != ModeApp { + return fmt.Errorf("pc=%3d dynamic cost budget exceeded, executing %s: local program cost was %d", + cx.pc, spec.Name, cx.cost) } + requiredExtraBudget := uint64(opcost - cx.remainingBudget()) + budgetPerMinFee := uint64(cx.Proto.MaxAppProgramCost) requiredMinFees := (requiredExtraBudget + budgetPerMinFee - 1) / budgetPerMinFee - shortfall := cx.Proto.MinTxnFee * requiredMinFees + requiredFee := cx.Proto.MinTxnFee * requiredMinFees - if cx.FeeCredit == nil || *cx.FeeCredit < shortfall { + if cx.FeeCredit == nil || *cx.FeeCredit < requiredFee { return fmt.Errorf("pc=%3d dynamic cost budget exceeded, executing %s: local program cost was %d", cx.pc, spec.Name, cx.cost) } - *cx.FeeCredit -= shortfall - - if cx.runMode == ModeApp { - *cx.pooledAllowedInners -= int(requiredMinFees) - if *cx.pooledAllowedInners < 0 { - return fmt.Errorf("pc=%3d dynamic group cost budget exceeded, executing %s: local program cost was %d", - cx.pc, spec.Name, cx.cost) - } + *cx.FeeCredit -= requiredFee - *cx.PooledApplicationBudget += int(budgetPerMinFee * requiredMinFees) - } else if cx.runMode == ModeSig { - // TODO: return error if we've used MaxTxGroupSize worth of lsig budget in the group - *cx.PooledLogicSigBudget += int(budgetPerMinFee * requiredMinFees) - } else { - return fmt.Errorf("unknown run mode %d", cx.runMode) + *cx.pooledAllowedInners -= int(requiredMinFees) + if *cx.pooledAllowedInners < 0 { + return fmt.Errorf("pc=%3d dynamic group cost budget exceeded, executing %s: local program cost was %d", + cx.pc, spec.Name, cx.cost) } + + *cx.PooledApplicationBudget += int(budgetPerMinFee * requiredMinFees) } cx.cost += opcost