summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Lee <64482439+algojohnlee@users.noreply.github.com>2021-08-10 09:08:32 -0400
committerGitHub <noreply@github.com>2021-08-10 09:08:32 -0400
commit7b53f6f029147fa4abd74739313a01aa7ee41715 (patch)
treebfc9315fa1361855e4746b86aeb5e12f56359af4
parent1ad773e0f9a11592e7ffdce749ed585183938105 (diff)
parent8ce1e6c69e5c4cf080fb170625e2551a387e9e63 (diff)
Merge pull request #2704 from algorand/hotfix/v2.9.1v2.9.1-stable
Hotfix/v2.9.1
-rw-r--r--.travis.yml4
-rw-r--r--buildnumber.dat2
-rw-r--r--config/consensus.go16
-rw-r--r--config/version.go2
-rw-r--r--daemon/algod/api/client/restClient.go8
-rw-r--r--data/basics/overflow.go32
-rw-r--r--data/basics/units_test.go18
-rw-r--r--data/transactions/transaction.go8
-rw-r--r--data/transactions/transaction_test.go49
-rw-r--r--ledger/appcow.go25
-rw-r--r--ledger/appcow_test.go4
-rw-r--r--ledger/apply/application.go60
-rw-r--r--ledger/apply/application_test.go275
-rw-r--r--ledger/apply/apply.go17
-rw-r--r--ledger/apply/asset.go21
-rw-r--r--ledger/apply/keyreg_test.go16
-rw-r--r--ledger/apply/mockBalances_test.go14
-rw-r--r--ledger/assetcow.go48
-rw-r--r--ledger/cow.go20
-rw-r--r--ledger/cow_test.go2
-rw-r--r--ledger/eval.go15
-rw-r--r--libgoal/libgoal.go10
-rw-r--r--protocol/consensus.go7
-rwxr-xr-xscripts/travis/run_tests.sh4
-rw-r--r--test/e2e-go/features/transactions/app_pages_test.go183
-rwxr-xr-xtest/scripts/e2e_subs/e2e-app-extra-pages.sh35
26 files changed, 713 insertions, 182 deletions
diff --git a/.travis.yml b/.travis.yml
index c71c66279..c4212d488 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -7,11 +7,11 @@ if: tag IS blank
stages:
- name: build_commit
- if: NOT (branch =~ /^rel\//) AND type != pull_request
+ if: NOT (branch =~ /^hotfix\//) AND NOT (branch =~ /^rel\//) AND type != pull_request
- name: build_pr
if: type = pull_request
- name: build_release
- if: branch =~ /^rel\// AND type != pull_request
+ if: (branch =~ /^hotfix\// OR branch =~ /^rel\//) AND type != pull_request
- name: deploy
if: branch =~ /^rel\// AND type != pull_request
- name: post_deploy
diff --git a/buildnumber.dat b/buildnumber.dat
index 573541ac9..d00491fd7 100644
--- a/buildnumber.dat
+++ b/buildnumber.dat
@@ -1 +1 @@
-0
+1
diff --git a/config/consensus.go b/config/consensus.go
index 5bee45ac6..b6954231b 100644
--- a/config/consensus.go
+++ b/config/consensus.go
@@ -381,6 +381,8 @@ type ConsensusParams struct {
// 5. checking that in the case of going online the VoteFirst is less or equal to the LastValid+1.
// 6. checking that in the case of going online the VoteFirst is less or equal to the next network round.
EnableKeyregCoherencyCheck bool
+
+ EnableExtraPagesOnAppUpdate bool
}
// PaysetCommitType enumerates possible ways for the block header to commit to
@@ -969,9 +971,21 @@ func initConsensusProtocols() {
// v27 can be upgraded to v28, with an update delay of 7 days ( see calculation above )
v27.ApprovedUpgrades[protocol.ConsensusV28] = 140000
+ // v29 fixes application update by using ExtraProgramPages in size calculations
+ v29 := v28
+ v29.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{}
+
+ // Enable ExtraProgramPages for application update
+ v29.EnableExtraPagesOnAppUpdate = true
+
+ Consensus[protocol.ConsensusV29] = v29
+
+ // v28 can be upgraded to v29, with an update delay of 3 days ( see calculation above )
+ v28.ApprovedUpgrades[protocol.ConsensusV29] = 60000
+
// ConsensusFuture is used to test features that are implemented
// but not yet released in a production protocol version.
- vFuture := v28
+ vFuture := v29
vFuture.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{}
// FilterTimeout for period 0 should take a new optimized, configured value, need to revisit this later
diff --git a/config/version.go b/config/version.go
index 6b19636e1..b2d9ca833 100644
--- a/config/version.go
+++ b/config/version.go
@@ -33,7 +33,7 @@ const VersionMajor = 2
// VersionMinor is the Minor semantic version number (x.#.z) - changed when backwards-compatible features are introduced.
// Not enforced until after initial public release (x > 0).
-const VersionMinor = 8
+const VersionMinor = 9
// Version is the type holding our full version information.
type Version struct {
diff --git a/daemon/algod/api/client/restClient.go b/daemon/algod/api/client/restClient.go
index b231e80a7..a00f25880 100644
--- a/daemon/algod/api/client/restClient.go
+++ b/daemon/algod/api/client/restClient.go
@@ -434,6 +434,14 @@ func (client RestClient) PendingTransactionInformation(transactionID string) (re
return
}
+// PendingTransactionInformationV2 gets information about a recently issued
+// transaction. See PendingTransactionInformation for more details.
+func (client RestClient) PendingTransactionInformationV2(transactionID string) (response generatedV2.PendingTransactionResponse, err error) {
+ transactionID = stripTransaction(transactionID)
+ err = client.get(&response, fmt.Sprintf("/v2/transactions/pending/%s", transactionID), nil)
+ return
+}
+
// SuggestedFee gets the recommended transaction fee from the node
func (client RestClient) SuggestedFee() (response v1.TransactionFee, err error) {
err = client.get(&response, "/v1/transactions/fee", nil)
diff --git a/data/basics/overflow.go b/data/basics/overflow.go
index 7e6ac9511..7a2a3df8b 100644
--- a/data/basics/overflow.go
+++ b/data/basics/overflow.go
@@ -33,6 +33,13 @@ func OAdd16(a uint16, b uint16) (res uint16, overflowed bool) {
return
}
+// OAdd32 adds 2 uint32 values with overflow detection
+func OAdd32(a uint32, b uint32) (res uint32, overflowed bool) {
+ res = a + b
+ overflowed = res < a
+ return
+}
+
// OAdd adds 2 values with overflow detection
func OAdd(a uint64, b uint64) (res uint64, overflowed bool) {
res = a + b
@@ -47,6 +54,13 @@ func OSub(a uint64, b uint64) (res uint64, overflowed bool) {
return
}
+// OSub32 subtracts b from a with overflow detection
+func OSub32(a uint32, b uint32) (res uint32, overflowed bool) {
+ res = a - b
+ overflowed = res > a
+ return
+}
+
// OMul multiplies 2 values with overflow detection
func OMul(a uint64, b uint64) (res uint64, overflowed bool) {
if b == 0 {
@@ -78,6 +92,15 @@ func AddSaturate(a uint64, b uint64) uint64 {
return res
}
+// AddSaturate32 adds 2 uint32 values with saturation on overflow
+func AddSaturate32(a uint32, b uint32) uint32 {
+ res, overflowed := OAdd32(a, b)
+ if overflowed {
+ return math.MaxUint32
+ }
+ return res
+}
+
// SubSaturate subtracts 2 values with saturation on underflow
func SubSaturate(a uint64, b uint64) uint64 {
res, overflowed := OSub(a, b)
@@ -87,6 +110,15 @@ func SubSaturate(a uint64, b uint64) uint64 {
return res
}
+// SubSaturate32 subtracts 2 uint32 values with saturation on underflow
+func SubSaturate32(a uint32, b uint32) uint32 {
+ res, overflowed := OSub32(a, b)
+ if overflowed {
+ return 0
+ }
+ return res
+}
+
// Add16 adds 2 uint16 values with overflow detection
func (t *OverflowTracker) Add16(a uint16, b uint16) uint16 {
res, overflowed := OAdd16(a, b)
diff --git a/data/basics/units_test.go b/data/basics/units_test.go
index 74e69c82e..b0b3a8885 100644
--- a/data/basics/units_test.go
+++ b/data/basics/units_test.go
@@ -17,6 +17,7 @@
package basics
import (
+ "math"
"testing"
"github.com/stretchr/testify/require"
@@ -30,6 +31,23 @@ func TestSubSaturate(t *testing.T) {
require.Equal(t, b.SubSaturate(a), Round(1))
}
+func TestSubSaturate32(t *testing.T) {
+ require.Equal(t, uint32(0), SubSaturate32(0, 1))
+ require.Equal(t, uint32(0), SubSaturate32(1, 2))
+ require.Equal(t, uint32(0), SubSaturate32(1, 1))
+ require.Equal(t, uint32(0), SubSaturate32(1, math.MaxUint32))
+ require.Equal(t, uint32(1), SubSaturate32(2, 1))
+ require.Equal(t, uint32(math.MaxUint32-1), SubSaturate32(math.MaxUint32, 1))
+}
+
+func TestAddSaturate32(t *testing.T) {
+ require.Equal(t, uint32(1), AddSaturate32(0, 1))
+ require.Equal(t, uint32(math.MaxUint32-1), AddSaturate32(math.MaxUint32-2, 1))
+ require.Equal(t, uint32(math.MaxUint32), AddSaturate32(math.MaxUint32, 0))
+ require.Equal(t, uint32(math.MaxUint32), AddSaturate32(math.MaxUint32-1, 1))
+ require.Equal(t, uint32(math.MaxUint32), AddSaturate32(math.MaxUint32, 2))
+}
+
func TestRoundUpToMultipleOf(t *testing.T) {
r := Round(24)
for n := Round(1); n < Round(100); n++ {
diff --git a/data/transactions/transaction.go b/data/transactions/transaction.go
index c0e3e863f..552255796 100644
--- a/data/transactions/transaction.go
+++ b/data/transactions/transaction.go
@@ -344,6 +344,7 @@ func (tx Transaction) WellFormed(spec SpecialAddresses, proto config.ConsensusPa
}
}
+ effectiveEPP := tx.ExtraProgramPages
// Schemas and ExtraProgramPages may only be set during application creation
if tx.ApplicationID != 0 {
if tx.LocalStateSchema != (basics.StateSchema{}) ||
@@ -353,6 +354,11 @@ func (tx Transaction) WellFormed(spec SpecialAddresses, proto config.ConsensusPa
if tx.ExtraProgramPages != 0 {
return fmt.Errorf("tx.ExtraProgramPages is immutable")
}
+
+ if proto.EnableExtraPagesOnAppUpdate {
+ effectiveEPP = uint32(proto.MaxExtraAppProgramPages)
+ }
+
}
// Limit total number of arguments
@@ -396,7 +402,7 @@ func (tx Transaction) WellFormed(spec SpecialAddresses, proto config.ConsensusPa
lap := len(tx.ApprovalProgram)
lcs := len(tx.ClearStateProgram)
- pages := int(1 + tx.ExtraProgramPages)
+ pages := int(1 + effectiveEPP)
if lap > pages*proto.MaxAppProgramLen {
return fmt.Errorf("approval program too long. max len %d bytes", pages*proto.MaxAppProgramLen)
}
diff --git a/data/transactions/transaction_test.go b/data/transactions/transaction_test.go
index 5e8151467..26631dee4 100644
--- a/data/transactions/transaction_test.go
+++ b/data/transactions/transaction_test.go
@@ -228,6 +228,7 @@ func TestWellFormedErrors(t *testing.T) {
curProto := config.Consensus[protocol.ConsensusCurrentVersion]
futureProto := config.Consensus[protocol.ConsensusFuture]
protoV27 := config.Consensus[protocol.ConsensusV27]
+ protoV28 := config.Consensus[protocol.ConsensusV28]
addr1, err := basics.UnmarshalChecksumAddress("NDQCJNNY5WWWFLP4GFZ7MEF2QJSMZYK6OWIV2AQ7OMAVLEFCGGRHFPKJJA")
require.NoError(t, err)
okHeader := Header{
@@ -447,6 +448,54 @@ func TestWellFormedErrors(t *testing.T) {
proto: futureProto,
expectedError: fmt.Errorf("tx has too many references, max is 8"),
},
+ {
+ tx: Transaction{
+ Type: protocol.ApplicationCallTx,
+ Header: okHeader,
+ ApplicationCallTxnFields: ApplicationCallTxnFields{
+ ApplicationID: 1,
+ ApprovalProgram: []byte(strings.Repeat("X", 1025)),
+ ClearStateProgram: []byte(strings.Repeat("X", 1025)),
+ ExtraProgramPages: 0,
+ OnCompletion: UpdateApplicationOC,
+ },
+ },
+ spec: specialAddr,
+ proto: protoV28,
+ expectedError: fmt.Errorf("app programs too long. max total len %d bytes", curProto.MaxAppProgramLen),
+ },
+ {
+ tx: Transaction{
+ Type: protocol.ApplicationCallTx,
+ Header: okHeader,
+ ApplicationCallTxnFields: ApplicationCallTxnFields{
+ ApplicationID: 1,
+ ApprovalProgram: []byte(strings.Repeat("X", 1025)),
+ ClearStateProgram: []byte(strings.Repeat("X", 1025)),
+ ExtraProgramPages: 0,
+ OnCompletion: UpdateApplicationOC,
+ },
+ },
+ spec: specialAddr,
+ proto: futureProto,
+ },
+ {
+ tx: Transaction{
+ Type: protocol.ApplicationCallTx,
+ Header: okHeader,
+ ApplicationCallTxnFields: ApplicationCallTxnFields{
+ ApplicationID: 1,
+ ApplicationArgs: [][]byte{
+ []byte("write"),
+ },
+ ExtraProgramPages: 1,
+ OnCompletion: UpdateApplicationOC,
+ },
+ },
+ spec: specialAddr,
+ proto: protoV28,
+ expectedError: fmt.Errorf("tx.ExtraProgramPages is immutable"),
+ },
}
for _, usecase := range usecases {
err := usecase.tx.WellFormed(usecase.spec, usecase.proto)
diff --git a/ledger/appcow.go b/ledger/appcow.go
index eafa0930f..4b41c92a5 100644
--- a/ledger/appcow.go
+++ b/ledger/appcow.go
@@ -222,7 +222,7 @@ func errAlreadyStorage(addr basics.Address, aidx basics.AppIndex, global bool) e
}
// Allocate creates kv storage for a given {addr, aidx, global}. It is called on app creation (global) or opting in (local)
-func (cb *roundCowState) Allocate(addr basics.Address, aidx basics.AppIndex, global bool, space basics.StateSchema) error {
+func (cb *roundCowState) AllocateApp(addr basics.Address, aidx basics.AppIndex, global bool, space basics.StateSchema) error {
// Check that account is not already opted in
allocated, err := cb.allocated(addr, aidx, global)
if err != nil {
@@ -241,11 +241,21 @@ func (cb *roundCowState) Allocate(addr basics.Address, aidx basics.AppIndex, glo
lsd.action = allocAction
lsd.maxCounts = &space
+ if global {
+ cb.mods.Creatables[basics.CreatableIndex(aidx)] = ledgercore.ModifiedCreatable{
+ Ctype: basics.AppCreatable,
+ Creator: addr,
+ Created: true,
+ }
+ }
+
+ cb.trackCreatable(basics.CreatableIndex(aidx))
+
return nil
}
// Deallocate clears storage for {addr, aidx, global}. It happens on app deletion (global) or closing out (local)
-func (cb *roundCowState) Deallocate(addr basics.Address, aidx basics.AppIndex, global bool) error {
+func (cb *roundCowState) DeallocateApp(addr basics.Address, aidx basics.AppIndex, global bool) error {
// Check that account has allocated storage
allocated, err := cb.allocated(addr, aidx, global)
if err != nil {
@@ -265,6 +275,15 @@ func (cb *roundCowState) Deallocate(addr basics.Address, aidx basics.AppIndex, g
lsd.counts = &basics.StateSchema{}
lsd.maxCounts = &basics.StateSchema{}
lsd.kvCow = make(stateDelta)
+
+ if global {
+ cb.mods.Creatables[basics.CreatableIndex(aidx)] = ledgercore.ModifiedCreatable{
+ Ctype: basics.AppCreatable,
+ Creator: addr,
+ Created: false,
+ }
+ }
+
return nil
}
@@ -619,7 +638,7 @@ func applyStorageDelta(data basics.AccountData, aapp storagePtr, store *storageD
delete(owned, aapp.aidx)
case allocAction, remainAllocAction:
// note: these should always exist because they were
- // at least preceded by a call to PutWithCreatable
+ // at least preceded by a call to Put()
params, ok := owned[aapp.aidx]
if !ok {
return basics.AccountData{}, fmt.Errorf("could not find existing params for %v", aapp.aidx)
diff --git a/ledger/appcow_test.go b/ledger/appcow_test.go
index 75dbfac91..0994ed2f6 100644
--- a/ledger/appcow_test.go
+++ b/ledger/appcow_test.go
@@ -239,7 +239,7 @@ func TestCowStorage(t *testing.T) {
NumUint: rand.Uint64(),
NumByteSlice: rand.Uint64(),
}
- err := cow.Allocate(addr, sptr.aidx, sptr.global, rschema)
+ err := cow.AllocateApp(addr, sptr.aidx, sptr.global, rschema)
if actuallyAllocated {
require.Error(t, err)
require.Contains(t, err.Error(), "cannot allocate")
@@ -253,7 +253,7 @@ func TestCowStorage(t *testing.T) {
// Deallocate
if rand.Float32() < 0.25 {
actuallyAllocated := st.allocated(aapp)
- err := cow.Deallocate(addr, sptr.aidx, sptr.global)
+ err := cow.DeallocateApp(addr, sptr.aidx, sptr.global)
if actuallyAllocated {
require.NoError(t, err)
err := st.dealloc(aapp)
diff --git a/ledger/apply/application.go b/ledger/apply/application.go
index dc9da3122..644c8e396 100644
--- a/ledger/apply/application.go
+++ b/ledger/apply/application.go
@@ -118,24 +118,17 @@ func createApplication(ac *transactions.ApplicationCallTxnFields, balances Balan
// Update the cached TotalExtraAppPages for this account, used
// when computing MinBalance
totalExtraPages := record.TotalExtraAppPages
- totalExtraPages += ac.ExtraProgramPages
+ totalExtraPages = basics.AddSaturate32(totalExtraPages, ac.ExtraProgramPages)
record.TotalExtraAppPages = totalExtraPages
- // Tell the cow what app we created
- created := &basics.CreatableLocator{
- Creator: creator,
- Type: basics.AppCreatable,
- Index: basics.CreatableIndex(appIdx),
- }
-
// Write back to the creator's balance record
- err = balances.PutWithCreatable(creator, record, created, nil)
+ err = balances.Put(creator, record)
if err != nil {
return 0, err
}
// Allocate global storage
- err = balances.Allocate(creator, appIdx, true, ac.GlobalStateSchema)
+ err = balances.AllocateApp(creator, appIdx, true, ac.GlobalStateSchema)
if err != nil {
return 0, err
}
@@ -150,6 +143,8 @@ func deleteApplication(balances Balances, creator basics.Address, appIdx basics.
return err
}
+ record.AppParams = cloneAppParams(record.AppParams)
+
// Update the TotalAppSchema used for MinBalance calculation,
// since the creator no longer has to store the GlobalState
totalSchema := record.TotalAppSchema
@@ -157,31 +152,27 @@ func deleteApplication(balances Balances, creator basics.Address, appIdx basics.
totalSchema = totalSchema.SubSchema(globalSchema)
record.TotalAppSchema = totalSchema
- // Delete the AppParams
- record.AppParams = cloneAppParams(record.AppParams)
- delete(record.AppParams, appIdx)
-
// Delete app's extra program pages
totalExtraPages := record.TotalExtraAppPages
if totalExtraPages > 0 {
- extraPages := record.AppParams[appIdx].ExtraProgramPages
- totalExtraPages -= extraPages
+ proto := balances.ConsensusParams()
+ if proto.EnableExtraPagesOnAppUpdate {
+ extraPages := record.AppParams[appIdx].ExtraProgramPages
+ totalExtraPages = basics.SubSaturate32(totalExtraPages, extraPages)
+ }
record.TotalExtraAppPages = totalExtraPages
}
- // Tell the cow what app we deleted
- deleted := &basics.CreatableLocator{
- Creator: creator,
- Type: basics.AppCreatable,
- Index: basics.CreatableIndex(appIdx),
- }
- err = balances.PutWithCreatable(creator, record, nil, deleted)
+ // Delete the AppParams
+ delete(record.AppParams, appIdx)
+
+ err = balances.Put(creator, record)
if err != nil {
return err
}
// Deallocate global storage
- err = balances.Deallocate(creator, appIdx, true)
+ err = balances.DeallocateApp(creator, appIdx, true)
if err != nil {
return err
}
@@ -199,6 +190,23 @@ func updateApplication(ac *transactions.ApplicationCallTxnFields, balances Balan
// Fill in the new programs
record.AppParams = cloneAppParams(record.AppParams)
params := record.AppParams[appIdx]
+ proto := balances.ConsensusParams()
+ // when proto.EnableExtraPageOnAppUpdate is false, WellFormed rejects all updates with a multiple-page program
+ if proto.EnableExtraPagesOnAppUpdate {
+ lap := len(ac.ApprovalProgram)
+ lcs := len(ac.ClearStateProgram)
+ pages := int(1 + params.ExtraProgramPages)
+ if lap > pages*proto.MaxAppProgramLen {
+ return fmt.Errorf("updateApplication approval program too long. max len %d bytes", pages*proto.MaxAppProgramLen)
+ }
+ if lcs > pages*proto.MaxAppProgramLen {
+ return fmt.Errorf("updateApplication clear state program too long. max len %d bytes", pages*proto.MaxAppProgramLen)
+ }
+ if lap+lcs > pages*proto.MaxAppTotalProgramLen {
+ return fmt.Errorf("updateApplication app programs too long, %d. max total len %d bytes", lap+lcs, pages*proto.MaxAppTotalProgramLen)
+ }
+ }
+
params.ApprovalProgram = ac.ApprovalProgram
params.ClearStateProgram = ac.ClearStateProgram
@@ -243,7 +251,7 @@ func optInApplication(balances Balances, sender basics.Address, appIdx basics.Ap
}
// Allocate local storage
- err = balances.Allocate(sender, appIdx, false, params.LocalStateSchema)
+ err = balances.AllocateApp(sender, appIdx, false, params.LocalStateSchema)
if err != nil {
return err
}
@@ -281,7 +289,7 @@ func closeOutApplication(balances Balances, sender basics.Address, appIdx basics
}
// Deallocate local storage
- err = balances.Deallocate(sender, appIdx, false)
+ err = balances.DeallocateApp(sender, appIdx, false)
if err != nil {
return err
}
diff --git a/ledger/apply/application_test.go b/ledger/apply/application_test.go
index 0633db8d1..8b320b6e2 100644
--- a/ledger/apply/application_test.go
+++ b/ledger/apply/application_test.go
@@ -103,11 +103,9 @@ type testBalances struct {
proto config.ConsensusParams
put int // Put calls counter
- putWith int // PutWithCreatable calls counter
putBalances map[basics.Address]basics.AccountData
- putWithBalances map[basics.Address]basics.AccountData
- putWithNew []basics.CreatableLocator
- putWithDel []basics.CreatableLocator
+ createdCreatables []basics.CreatableLocator
+ deletedCreatables []basics.CreatableLocator
allocatedAppIdx basics.AppIndex
deAllocatedAppIdx basics.AppIndex
@@ -141,21 +139,6 @@ func (b *testBalances) Put(addr basics.Address, ad basics.AccountData) error {
return nil
}
-func (b *testBalances) PutWithCreatable(addr basics.Address, ad basics.AccountData, newCreatable *basics.CreatableLocator, deletedCreatable *basics.CreatableLocator) error {
- b.putWith++
- if b.putWithBalances == nil {
- b.putWithBalances = make(map[basics.Address]basics.AccountData)
- }
- b.putWithBalances[addr] = ad
- if newCreatable != nil {
- b.putWithNew = append(b.putWithNew, *newCreatable)
- }
- if deletedCreatable != nil {
- b.putWithDel = append(b.putWithDel, *deletedCreatable)
- }
- return nil
-}
-
func (b *testBalances) GetCreator(cidx basics.CreatableIndex, ctype basics.CreatableType) (basics.Address, bool, error) {
if ctype == basics.AppCreatable {
aidx := basics.AppIndex(cidx)
@@ -176,13 +159,60 @@ func (b *testBalances) Move(src, dst basics.Address, amount basics.MicroAlgos, s
func (b *testBalances) ConsensusParams() config.ConsensusParams {
return b.proto
}
-func (b *testBalances) Allocate(addr basics.Address, aidx basics.AppIndex, global bool, space basics.StateSchema) error {
+
+func (b *testBalances) AllocateApp(addr basics.Address, aidx basics.AppIndex, global bool, space basics.StateSchema) error {
b.allocatedAppIdx = aidx
+
+ if global {
+ locator := basics.CreatableLocator{
+ Type: basics.AppCreatable,
+ Creator: addr,
+ Index: basics.CreatableIndex(aidx),
+ }
+ b.createdCreatables = append(b.createdCreatables, locator)
+ }
+
return nil
}
-func (b *testBalances) Deallocate(addr basics.Address, aidx basics.AppIndex, global bool) error {
+func (b *testBalances) DeallocateApp(addr basics.Address, aidx basics.AppIndex, global bool) error {
b.deAllocatedAppIdx = aidx
+
+ if global {
+ locator := basics.CreatableLocator{
+ Type: basics.AppCreatable,
+ Creator: addr,
+ Index: basics.CreatableIndex(aidx),
+ }
+ b.deletedCreatables = append(b.deletedCreatables, locator)
+ }
+
+ return nil
+}
+
+func (b *testBalances) AllocateAsset(addr basics.Address, index basics.AssetIndex, global bool) error {
+ if global {
+ locator := basics.CreatableLocator{
+ Type: basics.AppCreatable,
+ Creator: addr,
+ Index: basics.CreatableIndex(index),
+ }
+ b.createdCreatables = append(b.createdCreatables, locator)
+ }
+
+ return nil
+}
+
+func (b *testBalances) DeallocateAsset(addr basics.Address, index basics.AssetIndex, global bool) error {
+ if global {
+ locator := basics.CreatableLocator{
+ Type: basics.AppCreatable,
+ Creator: addr,
+ Index: basics.CreatableIndex(index),
+ }
+ b.deletedCreatables = append(b.deletedCreatables, locator)
+ }
+
return nil
}
@@ -206,14 +236,6 @@ func (b *testBalancesPass) Put(addr basics.Address, ad basics.AccountData) error
return nil
}
-func (b *testBalancesPass) PutWithCreatable(addr basics.Address, ad basics.AccountData, newCreatable *basics.CreatableLocator, deletedCreatable *basics.CreatableLocator) error {
- if b.balances == nil {
- b.balances = make(map[basics.Address]basics.AccountData)
- }
- b.balances[addr] = ad
- return nil
-}
-
func (b *testBalancesPass) ConsensusParams() config.ConsensusParams {
return b.proto
}
@@ -230,14 +252,12 @@ func (b *testBalancesPass) StatefulEval(params logic.EvalParams, aidx basics.App
return true, b.delta, nil
}
-// ResetWrites clears side effects of Put/PutWithCreatable
+// ResetWrites clears side effects of Put.
func (b *testBalances) ResetWrites() {
b.put = 0
- b.putWith = 0
b.putBalances = nil
- b.putWithBalances = nil
- b.putWithNew = []basics.CreatableLocator{}
- b.putWithDel = []basics.CreatableLocator{}
+ b.createdCreatables = []basics.CreatableLocator{}
+ b.deletedCreatables = []basics.CreatableLocator{}
b.allocatedAppIdx = 0
}
@@ -245,6 +265,10 @@ func (b *testBalances) SetProto(name protocol.ConsensusVersion) {
b.proto = config.Consensus[name]
}
+func (b *testBalances) SetParams(params config.ConsensusParams) {
+ b.proto = params
+}
+
type testEvaluator struct {
pass bool
delta basics.EvalDelta
@@ -411,11 +435,8 @@ func TestAppCallCreate(t *testing.T) {
appIdx, err = createApplication(&ac, &b, creator, txnCounter)
a.NoError(err)
a.Equal(txnCounter+1, uint64(appIdx))
- a.Equal(0, b.put)
- a.Equal(1, b.putWith)
+ a.Equal(1, b.put)
nbr, ok := b.putBalances[creator]
- a.False(ok)
- nbr, ok = b.putWithBalances[creator]
a.True(ok)
params, ok := nbr.AppParams[appIdx]
a.True(ok)
@@ -423,7 +444,7 @@ func TestAppCallCreate(t *testing.T) {
a.Equal(ac.ClearStateProgram, params.ClearStateProgram)
a.Equal(ac.LocalStateSchema, params.LocalStateSchema)
a.Equal(ac.GlobalStateSchema, params.GlobalStateSchema)
- a.True(len(b.putWithNew) > 0)
+ a.Equal(1, len(b.createdCreatables))
}
// TestAppCallApplyCreate carefully tracks and validates balance record updates
@@ -448,7 +469,6 @@ func TestAppCallApplyCreate(t *testing.T) {
a.Error(err)
a.Contains(err.Error(), "ApplicationCall cannot have nil ApplyData")
a.Equal(0, b.put)
- a.Equal(0, b.putWith)
b.balances = make(map[basics.Address]basics.AccountData)
b.balances[creator] = basics.AccountData{}
@@ -458,7 +478,6 @@ func TestAppCallApplyCreate(t *testing.T) {
a.Error(err)
a.Contains(err.Error(), "max created apps per acct is 0")
a.Equal(0, b.put)
- a.Equal(0, b.putWith)
b.SetProto(protocol.ConsensusFuture)
proto := b.ConsensusParams()
@@ -470,14 +489,13 @@ func TestAppCallApplyCreate(t *testing.T) {
err = ApplicationCall(ac, h, &b, ad, &ep, txnCounter)
a.Error(err)
a.Contains(err.Error(), "applications that do not exist")
- a.Equal(0, b.put)
- a.Equal(1, b.putWith)
+ a.Equal(1, b.put)
appIdx := basics.AppIndex(txnCounter + 1)
b.appCreators = map[basics.AppIndex]basics.Address{appIdx: creator}
// save the created app info to the side
- saved := b.putWithBalances[creator]
+ saved := b.putBalances[creator]
b.ResetWrites()
@@ -486,8 +504,7 @@ func TestAppCallApplyCreate(t *testing.T) {
err = ApplicationCall(ac, h, &b, ad, &ep, txnCounter)
a.Error(err)
a.Contains(err.Error(), fmt.Sprintf("app %d not found in account", appIdx))
- a.Equal(0, b.put)
- a.Equal(1, b.putWith)
+ a.Equal(1, b.put)
b.ResetWrites()
@@ -500,15 +517,14 @@ func TestAppCallApplyCreate(t *testing.T) {
a.Error(err)
a.Contains(err.Error(), "transaction rejected by ApprovalProgram")
a.Equal(uint64(b.allocatedAppIdx), txnCounter+1)
- a.Equal(0, b.put)
- a.Equal(1, b.putWith)
+ a.Equal(1, b.put)
// ensure original balance record in the mock was not changed
// this ensure proper cloning and any in-intended in-memory modifications
//
// known artefact of cloning AppLocalState even with empty update, nil map vs empty map
saved.AppLocalStates = map[basics.AppIndex]basics.AppLocalState{}
a.Equal(saved, b.balances[creator])
- saved = b.putWithBalances[creator]
+ saved = b.putBalances[creator]
b.ResetWrites()
@@ -523,10 +539,9 @@ func TestAppCallApplyCreate(t *testing.T) {
err = ApplicationCall(ac, h, &b, ad, &ep, txnCounter)
a.NoError(err)
a.Equal(appIdx, b.allocatedAppIdx)
- a.Equal(0, b.put)
- a.Equal(1, b.putWith)
+ a.Equal(1, b.put)
a.Equal(saved, b.balances[creator])
- br := b.putWithBalances[creator]
+ br := b.putBalances[creator]
a.Equal([]byte{1}, br.AppParams[appIdx].ApprovalProgram)
a.Equal([]byte{1}, br.AppParams[appIdx].ClearStateProgram)
a.Equal(basics.TealKeyValue(nil), br.AppParams[appIdx].GlobalState)
@@ -538,7 +553,7 @@ func TestAppCallApplyCreate(t *testing.T) {
ac.ExtraProgramPages = 1
err = ApplicationCall(ac, h, &b, ad, &ep, txnCounter)
a.NoError(err)
- br = b.putWithBalances[creator]
+ br = b.putBalances[creator]
a.Equal(uint32(1), br.AppParams[appIdx].ExtraProgramPages)
a.Equal(uint32(1), br.TotalExtraAppPages)
}
@@ -606,13 +621,11 @@ func TestAppCallOptIn(t *testing.T) {
a.Error(err)
a.Contains(err.Error(), "cannot opt in app")
a.Equal(0, b.put)
- a.Equal(0, b.putWith)
b.SetProto(protocol.ConsensusFuture)
err = optInApplication(&b, sender, appIdx, params)
a.NoError(err)
a.Equal(1, b.put)
- a.Equal(0, b.putWith)
br := b.putBalances[sender]
a.Equal(basics.AccountData{AppLocalStates: map[basics.AppIndex]basics.AppLocalState{appIdx: {}}}, br)
@@ -625,7 +638,6 @@ func TestAppCallOptIn(t *testing.T) {
a.Error(err)
a.Contains(err.Error(), "has already opted in to app")
a.Equal(0, b.put)
- a.Equal(0, b.putWith)
b.ResetWrites()
@@ -635,7 +647,6 @@ func TestAppCallOptIn(t *testing.T) {
err = optInApplication(&b, sender, appIdx, params)
a.NoError(err)
a.Equal(1, b.put)
- a.Equal(0, b.putWith)
b.ResetWrites()
@@ -648,7 +659,6 @@ func TestAppCallOptIn(t *testing.T) {
err = optInApplication(&b, sender, appIdx, params)
a.NoError(err)
a.Equal(1, b.put)
- a.Equal(0, b.putWith)
br = b.putBalances[sender]
a.Equal(
basics.AccountData{
@@ -707,7 +717,6 @@ func TestAppCallClearState(t *testing.T) {
a.Error(err)
a.Contains(err.Error(), "is not currently opted in to app")
a.Equal(0, b.put)
- a.Equal(0, b.putWith)
// check non-existing app with empty opt-in
b.balances[sender] = basics.AccountData{
@@ -716,7 +725,6 @@ func TestAppCallClearState(t *testing.T) {
err = ApplicationCall(ac, h, &b, ad, &ep, txnCounter)
a.NoError(err)
a.Equal(1, b.put)
- a.Equal(0, b.putWith)
br := b.putBalances[sender]
a.Equal(0, len(br.AppLocalStates))
a.Equal(basics.StateSchema{}, br.TotalAppSchema)
@@ -735,7 +743,6 @@ func TestAppCallClearState(t *testing.T) {
err = ApplicationCall(ac, h, &b, ad, &ep, txnCounter)
a.NoError(err)
a.Equal(1, b.put)
- a.Equal(0, b.putWith)
br = b.putBalances[sender]
a.Equal(0, len(br.AppLocalStates))
a.Equal(basics.StateSchema{}, br.TotalAppSchema)
@@ -762,7 +769,6 @@ func TestAppCallClearState(t *testing.T) {
err = ApplicationCall(ac, h, &b, ad, &ep, txnCounter)
a.NoError(err)
a.Equal(1, b.put)
- a.Equal(0, b.putWith)
br = b.putBalances[sender]
a.Equal(0, len(br.AppLocalStates))
a.Equal(basics.StateSchema{}, br.TotalAppSchema)
@@ -778,7 +784,6 @@ func TestAppCallClearState(t *testing.T) {
err = ApplicationCall(ac, h, &b, ad, &ep, txnCounter)
a.NoError(err)
a.Equal(1, b.put)
- a.Equal(0, b.putWith)
br = b.putBalances[sender]
a.Equal(0, len(br.AppLocalStates))
a.Equal(basics.StateSchema{}, br.TotalAppSchema)
@@ -809,7 +814,6 @@ func TestAppCallClearState(t *testing.T) {
err = ApplicationCall(ac, h, &b, ad, &ep, txnCounter)
a.NoError(err)
a.Equal(1, b.put)
- a.Equal(0, b.putWith)
a.Equal(appIdx, b.deAllocatedAppIdx)
a.Equal(0, len(br.AppLocalStates))
a.Equal(basics.StateSchema{}, br.TotalAppSchema)
@@ -860,7 +864,6 @@ func TestAppCallApplyCloseOut(t *testing.T) {
a.Error(err)
a.Contains(err.Error(), "transaction rejected by ApprovalProgram")
a.Equal(0, b.put)
- a.Equal(0, b.putWith)
br := b.balances[creator]
a.Equal(cbr, br)
a.Equal(basics.EvalDelta{}, ad.EvalDelta)
@@ -872,7 +875,6 @@ func TestAppCallApplyCloseOut(t *testing.T) {
a.Error(err)
a.Contains(err.Error(), "is not opted in to app")
a.Equal(0, b.put)
- a.Equal(0, b.putWith)
br = b.balances[creator]
a.Equal(cbr, br)
a.Equal(basics.EvalDelta{}, ad.EvalDelta)
@@ -888,7 +890,6 @@ func TestAppCallApplyCloseOut(t *testing.T) {
err = ApplicationCall(ac, h, &b, ad, &ep, txnCounter)
a.NoError(err)
a.Equal(1, b.put)
- a.Equal(0, b.putWith)
br = b.putBalances[creator]
a.NotEqual(cbr, br)
a.Equal(basics.TealKeyValue(nil), br.AppParams[appIdx].GlobalState)
@@ -896,6 +897,7 @@ func TestAppCallApplyCloseOut(t *testing.T) {
a.Equal(0, len(br.AppLocalStates))
a.Equal(basics.EvalDelta{GlobalDelta: gd}, ad.EvalDelta)
a.Equal(basics.StateSchema{NumUint: 0}, br.TotalAppSchema)
+
}
func TestAppCallApplyUpdate(t *testing.T) {
@@ -935,7 +937,7 @@ func TestAppCallApplyUpdate(t *testing.T) {
b.balances[creator] = cp
b.appCreators = map[basics.AppIndex]basics.Address{appIdx: creator}
- b.SetProto(protocol.ConsensusFuture)
+ b.SetProto(protocol.ConsensusV28)
proto := b.ConsensusParams()
ep.Proto = &proto
@@ -944,7 +946,6 @@ func TestAppCallApplyUpdate(t *testing.T) {
a.Error(err)
a.Contains(err.Error(), "transaction rejected by ApprovalProgram")
a.Equal(0, b.put)
- a.Equal(0, b.putWith)
br := b.balances[creator]
a.Equal(cbr, br)
a.Equal(basics.EvalDelta{}, ad.EvalDelta)
@@ -955,13 +956,90 @@ func TestAppCallApplyUpdate(t *testing.T) {
err = ApplicationCall(ac, h, &b, ad, &ep, txnCounter)
a.NoError(err)
a.Equal(1, b.put)
- a.Equal(0, b.putWith)
br = b.balances[creator]
a.Equal(cbr, br)
br = b.putBalances[creator]
a.Equal([]byte{2}, br.AppParams[appIdx].ApprovalProgram)
a.Equal([]byte{2}, br.AppParams[appIdx].ClearStateProgram)
a.Equal(basics.EvalDelta{}, ad.EvalDelta)
+
+ //check program len check happens in future consensus proto version
+ b.SetProto(protocol.ConsensusFuture)
+ proto = b.ConsensusParams()
+ ep.Proto = &proto
+
+ // check app program len
+ params = basics.AppParams{
+ ApprovalProgram: []byte{1},
+ StateSchemas: basics.StateSchemas{
+ GlobalStateSchema: basics.StateSchema{NumUint: 1},
+ },
+ ExtraProgramPages: 1,
+ }
+ h = transactions.Header{
+ Sender: sender,
+ }
+
+ b.balances = make(map[basics.Address]basics.AccountData)
+ cbr = basics.AccountData{
+ AppParams: map[basics.AppIndex]basics.AppParams{appIdx: params},
+ }
+ cp = basics.AccountData{
+ AppParams: map[basics.AppIndex]basics.AppParams{appIdx: params},
+ }
+ b.balances[creator] = cp
+ b.appCreators = map[basics.AppIndex]basics.Address{appIdx: creator}
+
+ // check extraProgramPages is used
+ appr := make([]byte, 2*proto.MaxAppProgramLen+1)
+ appr[0] = 4 // version 4
+
+ var tests = []struct {
+ name string
+ approval []byte
+ clear []byte
+ }{
+ {"approval", appr, []byte{2}},
+ {"clear state", []byte{2}, appr},
+ }
+ for _, test := range tests {
+ ac = transactions.ApplicationCallTxnFields{
+ ApplicationID: appIdx,
+ OnCompletion: transactions.UpdateApplicationOC,
+ ApprovalProgram: test.approval,
+ ClearStateProgram: test.clear,
+ }
+
+ b.pass = true
+ err = ApplicationCall(ac, h, &b, ad, &ep, txnCounter)
+ a.Error(err)
+ a.Contains(err.Error(), fmt.Sprintf("updateApplication %s program too long", test.name))
+ }
+
+ // check extraProgramPages allows length of proto.MaxAppProgramLen + 1
+ appr = make([]byte, proto.MaxAppProgramLen+1)
+ appr[0] = 4
+ ac = transactions.ApplicationCallTxnFields{
+ ApplicationID: appIdx,
+ OnCompletion: transactions.UpdateApplicationOC,
+ ApprovalProgram: appr,
+ ClearStateProgram: []byte{2},
+ }
+ b.pass = true
+ err = ApplicationCall(ac, h, &b, ad, &ep, txnCounter)
+ a.NoError(err)
+
+ // check extraProgramPages is used and long sum rejected
+ ac = transactions.ApplicationCallTxnFields{
+ ApplicationID: appIdx,
+ OnCompletion: transactions.UpdateApplicationOC,
+ ApprovalProgram: appr,
+ ClearStateProgram: appr,
+ }
+ b.pass = true
+ err = ApplicationCall(ac, h, &b, ad, &ep, txnCounter)
+ a.Error(err)
+ a.Contains(err.Error(), "updateApplication app programs too long")
}
func TestAppCallApplyDelete(t *testing.T) {
@@ -981,6 +1059,7 @@ func TestAppCallApplyDelete(t *testing.T) {
StateSchemas: basics.StateSchemas{
GlobalStateSchema: basics.StateSchema{NumUint: 1},
},
+ ExtraProgramPages: 1,
}
h := transactions.Header{
Sender: sender,
@@ -990,15 +1069,19 @@ func TestAppCallApplyDelete(t *testing.T) {
var b testBalances
b.balances = make(map[basics.Address]basics.AccountData)
+ // cbr is to ensure the original balance record is not modified but copied when updated in apply
cbr := basics.AccountData{
- AppParams: map[basics.AppIndex]basics.AppParams{appIdx: params},
+ AppParams: map[basics.AppIndex]basics.AppParams{appIdx: params},
+ TotalExtraAppPages: 1,
}
cp := basics.AccountData{
- AppParams: map[basics.AppIndex]basics.AppParams{appIdx: params},
+ AppParams: map[basics.AppIndex]basics.AppParams{appIdx: params},
+ TotalExtraAppPages: 1,
}
b.balances[creator] = cp
b.appCreators = map[basics.AppIndex]basics.Address{appIdx: creator}
+ // check if it fails nothing changes
b.SetProto(protocol.ConsensusFuture)
proto := b.ConsensusParams()
ep.Proto = &proto
@@ -1008,26 +1091,64 @@ func TestAppCallApplyDelete(t *testing.T) {
a.Error(err)
a.Contains(err.Error(), "transaction rejected by ApprovalProgram")
a.Equal(0, b.put)
- a.Equal(0, b.putWith)
br := b.balances[creator]
a.Equal(cbr, br)
a.Equal(basics.EvalDelta{}, ad.EvalDelta)
- // check deletion on empty balance record - happy case
+ // check calculation on ConsensusV28. TotalExtraAppPages does not change
+ b.SetProto(protocol.ConsensusV28)
+ proto = b.ConsensusParams()
+ ep.Proto = &proto
+
b.pass = true
b.balances[sender] = basics.AccountData{}
err = ApplicationCall(ac, h, &b, ad, &ep, txnCounter)
a.NoError(err)
a.Equal(appIdx, b.deAllocatedAppIdx)
- a.Equal(0, b.put)
- a.Equal(1, b.putWith)
+ a.Equal(1, b.put)
br = b.balances[creator]
a.Equal(cbr, br)
br = b.putBalances[creator]
a.Equal(basics.AppParams{}, br.AppParams[appIdx])
a.Equal(basics.StateSchema{}, br.TotalAppSchema)
a.Equal(basics.EvalDelta{}, ad.EvalDelta)
- a.Equal(uint32(0), br.TotalExtraAppPages)
+ a.Equal(uint32(1), br.TotalExtraAppPages)
+ b.ResetWrites()
+
+ b.SetProto(protocol.ConsensusFuture)
+ proto = b.ConsensusParams()
+ ep.Proto = &proto
+
+ // check deletion
+ for initTotalExtraPages := uint32(0); initTotalExtraPages < 3; initTotalExtraPages++ {
+ cbr = basics.AccountData{
+ AppParams: map[basics.AppIndex]basics.AppParams{appIdx: params},
+ TotalExtraAppPages: initTotalExtraPages,
+ }
+ cp := basics.AccountData{
+ AppParams: map[basics.AppIndex]basics.AppParams{appIdx: params},
+ TotalExtraAppPages: initTotalExtraPages,
+ }
+ b.balances[creator] = cp
+ b.pass = true
+ b.balances[sender] = basics.AccountData{}
+ err = ApplicationCall(ac, h, &b, ad, &ep, txnCounter)
+ a.NoError(err)
+ a.Equal(appIdx, b.deAllocatedAppIdx)
+ a.Equal(1, b.put)
+ br = b.balances[creator]
+ a.Equal(cbr, br)
+ br = b.putBalances[creator]
+ a.Equal(basics.AppParams{}, br.AppParams[appIdx])
+ a.Equal(basics.StateSchema{}, br.TotalAppSchema)
+ a.Equal(basics.EvalDelta{}, ad.EvalDelta)
+ if initTotalExtraPages <= params.ExtraProgramPages {
+ a.Equal(uint32(0), br.TotalExtraAppPages)
+ } else {
+ a.Equal(initTotalExtraPages-1, br.TotalExtraAppPages)
+ }
+ b.ResetWrites()
+ }
}
func TestAppCallApplyCreateClearState(t *testing.T) {
diff --git a/ledger/apply/apply.go b/ledger/apply/apply.go
index e7a01c6cf..739d7803c 100644
--- a/ledger/apply/apply.go
+++ b/ledger/apply/apply.go
@@ -33,21 +33,22 @@ type Balances interface {
Put(basics.Address, basics.AccountData) error
- // PutWithCreatable is like Put, but should be used when creating or deleting an asset or application.
- PutWithCreatable(addr basics.Address, acct basics.AccountData, newCreatable *basics.CreatableLocator, deletedCreatable *basics.CreatableLocator) error
-
// GetCreator gets the address of the account that created a given creatable
GetCreator(cidx basics.CreatableIndex, ctype basics.CreatableType) (basics.Address, bool, error)
- // Allocate or Deallocate either global or address-local app storage.
+ // Allocate or deallocate either global or address-local app storage.
//
- // PutWithCreatable(...) and then {Allocate/Deallocate}(..., ..., global=true)
+ // Put(...) and then {AllocateApp/DeallocateApp}(..., ..., global=true)
// creates/destroys an application.
//
- // Put(...) and then {Allocate/Deallocate}(..., ..., global=false)
+ // Put(...) and then {AllocateApp/DeallocateApp}(..., ..., global=false)
// opts into/closes out of an application.
- Allocate(addr basics.Address, aidx basics.AppIndex, global bool, space basics.StateSchema) error
- Deallocate(addr basics.Address, aidx basics.AppIndex, global bool) error
+ AllocateApp(addr basics.Address, aidx basics.AppIndex, global bool, space basics.StateSchema) error
+ DeallocateApp(addr basics.Address, aidx basics.AppIndex, global bool) error
+
+ // Similar to above, notify COW that global/local asset state was created.
+ AllocateAsset(addr basics.Address, index basics.AssetIndex, global bool) error
+ DeallocateAsset(addr basics.Address, index basics.AssetIndex, global bool) error
// StatefulEval executes a TEAL program in stateful mode on the balances.
// It returns whether the program passed and its error. It alo returns
diff --git a/ledger/apply/asset.go b/ledger/apply/asset.go
index 26d50acf2..4c74649b7 100644
--- a/ledger/apply/asset.go
+++ b/ledger/apply/asset.go
@@ -95,14 +95,13 @@ func AssetConfig(cc transactions.AssetConfigTxnFields, header transactions.Heade
return fmt.Errorf("too many assets in account: %d > %d", len(record.Assets), balances.ConsensusParams().MaxAssetsPerAccount)
}
- // Tell the cow what asset we created
- created := &basics.CreatableLocator{
- Creator: header.Sender,
- Type: basics.AssetCreatable,
- Index: basics.CreatableIndex(newidx),
+ err = balances.Put(header.Sender, record)
+ if err != nil {
+ return err
}
- return balances.PutWithCreatable(header.Sender, record, created, nil)
+ // Tell the cow what asset we created
+ return balances.AllocateAsset(header.Sender, newidx, true)
}
// Re-configuration and destroying must be done by the manager key.
@@ -123,7 +122,6 @@ func AssetConfig(cc transactions.AssetConfigTxnFields, header transactions.Heade
record.Assets = cloneAssetHoldings(record.Assets)
record.AssetParams = cloneAssetParams(record.AssetParams)
- var deleted *basics.CreatableLocator
if cc.AssetParams == (basics.AssetParams{}) {
// Destroying an asset. The creator account must hold
// the entire outstanding asset amount.
@@ -132,10 +130,9 @@ func AssetConfig(cc transactions.AssetConfigTxnFields, header transactions.Heade
}
// Tell the cow what asset we deleted
- deleted = &basics.CreatableLocator{
- Creator: creator,
- Type: basics.AssetCreatable,
- Index: basics.CreatableIndex(cc.ConfigAsset),
+ err = balances.DeallocateAsset(creator, cc.ConfigAsset, true)
+ if err != nil {
+ return err
}
delete(record.Assets, cc.ConfigAsset)
@@ -158,7 +155,7 @@ func AssetConfig(cc transactions.AssetConfigTxnFields, header transactions.Heade
record.AssetParams[cc.ConfigAsset] = params
}
- return balances.PutWithCreatable(creator, record, nil, deleted)
+ return balances.Put(creator, record)
}
func takeOut(balances Balances, addr basics.Address, asset basics.AssetIndex, amount uint64, bypassFreeze bool) error {
diff --git a/ledger/apply/keyreg_test.go b/ledger/apply/keyreg_test.go
index ce9185300..58e4e8f68 100644
--- a/ledger/apply/keyreg_test.go
+++ b/ledger/apply/keyreg_test.go
@@ -49,10 +49,6 @@ func (balances keyregTestBalances) Put(addr basics.Address, ad basics.AccountDat
return nil
}
-func (balances keyregTestBalances) PutWithCreatable(basics.Address, basics.AccountData, *basics.CreatableLocator, *basics.CreatableLocator) error {
- return nil
-}
-
func (balances keyregTestBalances) Move(src, dst basics.Address, amount basics.MicroAlgos, srcRewards, dstRewards *basics.MicroAlgos) error {
return nil
}
@@ -65,11 +61,19 @@ func (balances keyregTestBalances) Round() basics.Round {
return basics.Round(4294967296)
}
-func (balances keyregTestBalances) Allocate(basics.Address, basics.AppIndex, bool, basics.StateSchema) error {
+func (balances keyregTestBalances) AllocateApp(basics.Address, basics.AppIndex, bool, basics.StateSchema) error {
+ return nil
+}
+
+func (balances keyregTestBalances) DeallocateApp(basics.Address, basics.AppIndex, bool) error {
+ return nil
+}
+
+func (balances keyregTestBalances) AllocateAsset(addr basics.Address, index basics.AssetIndex, global bool) error {
return nil
}
-func (balances keyregTestBalances) Deallocate(basics.Address, basics.AppIndex, bool) error {
+func (balances keyregTestBalances) DeallocateAsset(addr basics.Address, index basics.AssetIndex, global bool) error {
return nil
}
diff --git a/ledger/apply/mockBalances_test.go b/ledger/apply/mockBalances_test.go
index fadc97fe1..a1b64a2f2 100644
--- a/ledger/apply/mockBalances_test.go
+++ b/ledger/apply/mockBalances_test.go
@@ -49,22 +49,26 @@ func (balances mockBalances) Round() basics.Round {
return basics.Round(8675309)
}
-func (balances mockBalances) Allocate(basics.Address, basics.AppIndex, bool, basics.StateSchema) error {
+func (balances mockBalances) AllocateApp(basics.Address, basics.AppIndex, bool, basics.StateSchema) error {
return nil
}
-func (balances mockBalances) Deallocate(basics.Address, basics.AppIndex, bool) error {
+func (balances mockBalances) DeallocateApp(basics.Address, basics.AppIndex, bool) error {
return nil
}
-func (balances mockBalances) StatefulEval(logic.EvalParams, basics.AppIndex, []byte) (bool, basics.EvalDelta, error) {
- return false, basics.EvalDelta{}, nil
+func (balances mockBalances) AllocateAsset(addr basics.Address, index basics.AssetIndex, global bool) error {
+ return nil
}
-func (balances mockBalances) PutWithCreatable(basics.Address, basics.AccountData, *basics.CreatableLocator, *basics.CreatableLocator) error {
+func (balances mockBalances) DeallocateAsset(addr basics.Address, index basics.AssetIndex, global bool) error {
return nil
}
+func (balances mockBalances) StatefulEval(logic.EvalParams, basics.AppIndex, []byte) (bool, basics.EvalDelta, error) {
+ return false, basics.EvalDelta{}, nil
+}
+
func (balances mockBalances) Get(addr basics.Address, withPendingRewards bool) (basics.AccountData, error) {
return balances.b[addr], nil
}
diff --git a/ledger/assetcow.go b/ledger/assetcow.go
new file mode 100644
index 000000000..d84ae3221
--- /dev/null
+++ b/ledger/assetcow.go
@@ -0,0 +1,48 @@
+// Copyright (C) 2019-2021 Algorand, Inc.
+// This file is part of go-algorand
+//
+// go-algorand is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// go-algorand is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with go-algorand. If not, see <https://www.gnu.org/licenses/>.
+
+package ledger
+
+import (
+ "github.com/algorand/go-algorand/data/basics"
+ "github.com/algorand/go-algorand/ledger/ledgercore"
+)
+
+func (cs *roundCowState) AllocateAsset(addr basics.Address, index basics.AssetIndex, global bool) error {
+ if global {
+ cs.mods.Creatables[basics.CreatableIndex(index)] = ledgercore.ModifiedCreatable{
+ Ctype: basics.AssetCreatable,
+ Creator: addr,
+ Created: true,
+ }
+
+ cs.trackCreatable(basics.CreatableIndex(index))
+ }
+
+ return nil
+}
+
+func (cs *roundCowState) DeallocateAsset(addr basics.Address, index basics.AssetIndex, global bool) error {
+ if global {
+ cs.mods.Creatables[basics.CreatableIndex(index)] = ledgercore.ModifiedCreatable{
+ Ctype: basics.AssetCreatable,
+ Creator: addr,
+ Created: false,
+ }
+ }
+
+ return nil
+}
diff --git a/ledger/cow.go b/ledger/cow.go
index ba17dafc5..56ff9e33b 100644
--- a/ledger/cow.go
+++ b/ledger/cow.go
@@ -194,26 +194,6 @@ func (cb *roundCowState) blockHdr(r basics.Round) (bookkeeping.BlockHeader, erro
return cb.lookupParent.blockHdr(r)
}
-func (cb *roundCowState) put(addr basics.Address, new basics.AccountData, newCreatable *basics.CreatableLocator, deletedCreatable *basics.CreatableLocator) {
- cb.mods.Accts.Upsert(addr, new)
-
- if newCreatable != nil {
- cb.mods.Creatables[newCreatable.Index] = ledgercore.ModifiedCreatable{
- Ctype: newCreatable.Type,
- Creator: newCreatable.Creator,
- Created: true,
- }
- }
-
- if deletedCreatable != nil {
- cb.mods.Creatables[deletedCreatable.Index] = ledgercore.ModifiedCreatable{
- Ctype: deletedCreatable.Type,
- Creator: deletedCreatable.Creator,
- Created: false,
- }
- }
-}
-
func (cb *roundCowState) trackCreatable(creatableIndex basics.CreatableIndex) {
cb.trackedCreatables[cb.groupIdx] = creatableIndex
}
diff --git a/ledger/cow_test.go b/ledger/cow_test.go
index b09c479e8..71e12530b 100644
--- a/ledger/cow_test.go
+++ b/ledger/cow_test.go
@@ -101,7 +101,7 @@ func checkCow(t *testing.T, cow *roundCowState, accts map[basics.Address]basics.
func applyUpdates(cow *roundCowState, updates ledgercore.AccountDeltas) {
for i := 0; i < updates.Len(); i++ {
addr, delta := updates.GetByIdx(i)
- cow.put(addr, delta, nil, nil)
+ cow.Put(addr, delta)
}
}
diff --git a/ledger/eval.go b/ledger/eval.go
index 3b1968a74..f75d7a57e 100644
--- a/ledger/eval.go
+++ b/ledger/eval.go
@@ -247,16 +247,7 @@ func (cs *roundCowState) GetCreator(cidx basics.CreatableIndex, ctype basics.Cre
}
func (cs *roundCowState) Put(addr basics.Address, acct basics.AccountData) error {
- return cs.PutWithCreatable(addr, acct, nil, nil)
-}
-
-func (cs *roundCowState) PutWithCreatable(addr basics.Address, acct basics.AccountData, newCreatable *basics.CreatableLocator, deletedCreatable *basics.CreatableLocator) error {
- cs.put(addr, acct, newCreatable, deletedCreatable)
-
- // store the creatable locator
- if newCreatable != nil {
- cs.trackCreatable(newCreatable.Index)
- }
+ cs.mods.Accts.Upsert(addr, acct)
return nil
}
@@ -283,7 +274,7 @@ func (cs *roundCowState) Move(from basics.Address, to basics.Address, amt basics
if overflowed {
return fmt.Errorf("overspend (account %v, data %+v, tried to spend %v)", from, fromBal, amt)
}
- cs.put(from, fromBalNew, nil, nil)
+ cs.Put(from, fromBalNew)
toBal, err := cs.lookup(to)
if err != nil {
@@ -304,7 +295,7 @@ func (cs *roundCowState) Move(from basics.Address, to basics.Address, amt basics
if overflowed {
return fmt.Errorf("balance overflow (account %v, data %+v, was going to receive %v)", to, toBal, amt)
}
- cs.put(to, toBalNew, nil, nil)
+ cs.Put(to, toBalNew)
return nil
}
diff --git a/libgoal/libgoal.go b/libgoal/libgoal.go
index 87cfcbaa6..7fe882b0f 100644
--- a/libgoal/libgoal.go
+++ b/libgoal/libgoal.go
@@ -746,6 +746,16 @@ func (c *Client) PendingTransactionInformation(txid string) (resp v1.Transaction
return
}
+// PendingTransactionInformationV2 returns information about a recently issued
+// transaction based on its txid.
+func (c *Client) PendingTransactionInformationV2(txid string) (resp generatedV2.PendingTransactionResponse, err error) {
+ algod, err := c.ensureAlgodClient()
+ if err == nil {
+ resp, err = algod.PendingTransactionInformationV2(txid)
+ }
+ return
+}
+
// Block takes a round and returns its block
func (c *Client) Block(round uint64) (resp v1.Block, err error) {
algod, err := c.ensureAlgodClient()
diff --git a/protocol/consensus.go b/protocol/consensus.go
index 52c38c002..47e6148eb 100644
--- a/protocol/consensus.go
+++ b/protocol/consensus.go
@@ -153,6 +153,11 @@ const ConsensusV28 = ConsensusVersion(
"https://github.com/algorandfoundation/specs/tree/65b4ab3266c52c56a0fa7d591754887d68faad0a",
)
+// ConsensusV29 fixes application update by using ExtraProgramPages in size calculations
+const ConsensusV29 = ConsensusVersion(
+ "https://github.com/algorandfoundation/specs/tree/abc54f79f9ad679d2d22f0fb9909fb005c16f8a1",
+)
+
// ConsensusFuture is a protocol that should not appear in any production
// network, but is used to test features before they are released.
const ConsensusFuture = ConsensusVersion(
@@ -165,7 +170,7 @@ const ConsensusFuture = ConsensusVersion(
// ConsensusCurrentVersion is the latest version and should be used
// when a specific version is not provided.
-const ConsensusCurrentVersion = ConsensusV28
+const ConsensusCurrentVersion = ConsensusV29
// Error is used to indicate that an unsupported protocol has been detected.
type Error ConsensusVersion
diff --git a/scripts/travis/run_tests.sh b/scripts/travis/run_tests.sh
index 0c6f278f4..cf47b0b97 100755
--- a/scripts/travis/run_tests.sh
+++ b/scripts/travis/run_tests.sh
@@ -4,14 +4,14 @@ set -e
if [ "${BUILD_TYPE}" = "integration" ]; then
# Run short tests when doing pull requests; leave the long testing for nightly runs.
- if [[ "${TRAVIS_BRANCH}" =~ ^rel/nightly ]]; then
+ if [[ "${TRAVIS_BRANCH}" =~ ^rel/nightly ]] || [[ "${TRAVIS_BRANCH}" =~ ^hotfix/ ]]; then
SHORTTEST=
else
SHORTTEST=-short
fi
export SHORTTEST
make integration
-elif [ "${TRAVIS_EVENT_TYPE}" = "cron" ] || [[ "${TRAVIS_BRANCH}" =~ ^rel/ ]]; then
+elif [ "${TRAVIS_EVENT_TYPE}" = "cron" ] || [[ "${TRAVIS_BRANCH}" =~ ^rel/ ]] || [[ "${TRAVIS_BRANCH}" =~ ^hotfix/ ]]; then
make fulltest -j2
else
make shorttest -j2
diff --git a/test/e2e-go/features/transactions/app_pages_test.go b/test/e2e-go/features/transactions/app_pages_test.go
new file mode 100644
index 000000000..17983c1e2
--- /dev/null
+++ b/test/e2e-go/features/transactions/app_pages_test.go
@@ -0,0 +1,183 @@
+// Copyright (C) 2019-2021 Algorand, Inc.
+// This file is part of go-algorand
+//
+// go-algorand is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// go-algorand is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with go-algorand. If not, see <https://www.gnu.org/licenses/>.
+
+package transactions
+
+import (
+ "encoding/base64"
+ "fmt"
+ "path/filepath"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+
+ "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/test/framework/fixtures"
+)
+
+func TestExtraProgramPages(t *testing.T) {
+ t.Parallel()
+ a := require.New(fixtures.SynchronizedTest(t))
+
+ var fixture fixtures.RestClientFixture
+ fixture.Setup(t, filepath.Join("nettemplates", "TwoNodes50EachFuture.json"))
+ defer fixture.Shutdown()
+ client := fixture.LibGoalClient
+
+ accountList, err := fixture.GetWalletsSortedByBalance()
+ a.NoError(err)
+ baseAcct := accountList[0].Address
+
+ walletHandle, err := client.GetUnencryptedWalletHandle()
+ a.NoError(err)
+
+ accountInfo, err := client.AccountInformationV2(baseAcct)
+ a.NoError(err)
+ if accountInfo.AppsTotalExtraPages != nil {
+ a.Equal(*accountInfo.AppsTotalExtraPages, uint64(0))
+ }
+
+ var inconsequentialBytes [2048]byte
+ srcBigProgram := fmt.Sprintf(`#pragma version 4
+byte base64(%s)
+pop
+int 1
+return
+`, base64.StdEncoding.EncodeToString(inconsequentialBytes[:]))
+
+ srcSmallProgram := `#pragma version 4
+int 1
+return
+`
+
+ bigProgramOps, err := logic.AssembleString(srcBigProgram)
+ a.NoError(err)
+ bigProgram := bigProgramOps.Program
+
+ smallProgramOps, err := logic.AssembleString(srcSmallProgram)
+ a.NoError(err)
+ smallProgram := smallProgramOps.Program
+
+ globalSchema := basics.StateSchema{
+ NumByteSlice: 1,
+ }
+ localSchema := basics.StateSchema{
+ NumByteSlice: 1,
+ }
+
+ status, err := client.Status()
+ a.NoError(err)
+
+ // create app 1 with 1 extra page
+ app1ExtraPages := uint32(1)
+ tx, err := client.MakeUnsignedAppCreateTx(transactions.NoOpOC, smallProgram, smallProgram, globalSchema, localSchema, nil, nil, nil, nil, app1ExtraPages)
+ a.NoError(err)
+ tx, err = client.FillUnsignedTxTemplate(baseAcct, 0, 0, 0, tx)
+ a.NoError(err)
+ txid, err := client.SignAndBroadcastTransaction(walletHandle, nil, tx)
+ a.NoError(err)
+ _, err = fixture.WaitForConfirmedTxn(status.LastRound+5, baseAcct, txid)
+ a.NoError(err)
+
+ app1CreateTxn, err := client.PendingTransactionInformationV2(txid)
+ a.NoError(err)
+ a.NotNil(app1CreateTxn.ConfirmedRound)
+ a.NotNil(app1CreateTxn.ApplicationIndex)
+ app1ID := *app1CreateTxn.ApplicationIndex
+
+ accountInfo, err = client.AccountInformationV2(baseAcct)
+ a.NoError(err)
+ a.NotNil(accountInfo.AppsTotalExtraPages)
+ a.Equal(*accountInfo.AppsTotalExtraPages, uint64(app1ExtraPages))
+
+ // update app 1 and ensure the extra page still works
+ tx, err = client.MakeUnsignedAppUpdateTx(app1ID, nil, nil, nil, nil, bigProgram, smallProgram)
+ a.NoError(err)
+ tx, err = client.FillUnsignedTxTemplate(baseAcct, 0, 0, 0, tx)
+ a.NoError(err)
+ txid, err = client.SignAndBroadcastTransaction(walletHandle, nil, tx)
+ a.NoError(err)
+ _, err = fixture.WaitForConfirmedTxn(*app1CreateTxn.ConfirmedRound+5, baseAcct, txid)
+ a.NoError(err)
+
+ app1UpdateTxn, err := client.PendingTransactionInformationV2(txid)
+ a.NoError(err)
+ a.NotNil(app1CreateTxn.ConfirmedRound)
+
+ accountInfo, err = client.AccountInformationV2(baseAcct)
+ a.NoError(err)
+ a.NotNil(accountInfo.AppsTotalExtraPages)
+ a.Equal(*accountInfo.AppsTotalExtraPages, uint64(app1ExtraPages))
+
+ // create app 2 with 2 extra pages
+ app2ExtraPages := uint32(2)
+ tx, err = client.MakeUnsignedAppCreateTx(transactions.NoOpOC, bigProgram, smallProgram, globalSchema, localSchema, nil, nil, nil, nil, app2ExtraPages)
+ a.NoError(err)
+ tx, err = client.FillUnsignedTxTemplate(baseAcct, 0, 0, 0, tx)
+ a.NoError(err)
+ txid, err = client.SignAndBroadcastTransaction(walletHandle, nil, tx)
+ a.NoError(err)
+ _, err = fixture.WaitForConfirmedTxn(*app1UpdateTxn.ConfirmedRound+5, baseAcct, txid)
+ a.NoError(err)
+
+ app2CreateTxn, err := client.PendingTransactionInformationV2(txid)
+ a.NoError(err)
+ a.NotNil(app2CreateTxn.ConfirmedRound)
+ a.NotNil(app2CreateTxn.ApplicationIndex)
+ app2ID := *app2CreateTxn.ApplicationIndex
+
+ accountInfo, err = client.AccountInformationV2(baseAcct)
+ a.NoError(err)
+ a.NotNil(accountInfo.AppsTotalExtraPages)
+ a.Equal(*accountInfo.AppsTotalExtraPages, uint64(app1ExtraPages+app2ExtraPages))
+
+ // delete app 1
+ tx, err = client.MakeUnsignedAppDeleteTx(app1ID, nil, nil, nil, nil)
+ a.NoError(err)
+ tx, err = client.FillUnsignedTxTemplate(baseAcct, 0, 0, 0, tx)
+ a.NoError(err)
+ txid, err = client.SignAndBroadcastTransaction(walletHandle, nil, tx)
+ a.NoError(err)
+ _, err = fixture.WaitForConfirmedTxn(*app2CreateTxn.ConfirmedRound+5, baseAcct, txid)
+ a.NoError(err)
+
+ app1DeleteTxn, err := client.PendingTransactionInformationV2(txid)
+ a.NoError(err)
+ a.NotNil(app1DeleteTxn.ConfirmedRound)
+
+ accountInfo, err = client.AccountInformationV2(baseAcct)
+ a.NoError(err)
+ a.NotNil(accountInfo.AppsTotalExtraPages)
+ a.Equal(*accountInfo.AppsTotalExtraPages, uint64(app2ExtraPages))
+
+ // delete app 2
+ tx, err = client.MakeUnsignedAppDeleteTx(app2ID, nil, nil, nil, nil)
+ a.NoError(err)
+ tx, err = client.FillUnsignedTxTemplate(baseAcct, 0, 0, 0, tx)
+ a.NoError(err)
+ txid, err = client.SignAndBroadcastTransaction(walletHandle, nil, tx)
+ a.NoError(err)
+ _, err = fixture.WaitForConfirmedTxn(*app1DeleteTxn.ConfirmedRound+5, baseAcct, txid)
+ a.NoError(err)
+
+ accountInfo, err = client.AccountInformationV2(baseAcct)
+ a.NoError(err)
+ if accountInfo.AppsTotalExtraPages != nil {
+ a.Equal(*accountInfo.AppsTotalExtraPages, uint64(0))
+ }
+}
diff --git a/test/scripts/e2e_subs/e2e-app-extra-pages.sh b/test/scripts/e2e_subs/e2e-app-extra-pages.sh
index 0fafa3256..9c32cef59 100755
--- a/test/scripts/e2e_subs/e2e-app-extra-pages.sh
+++ b/test/scripts/e2e_subs/e2e-app-extra-pages.sh
@@ -42,10 +42,14 @@ function generate_teal() {
BIG_TEAL_FILE="$TEMPDIR/big-app.teal"
BIG_TEAL_V4_FILE="$TEMPDIR/big-app-v4.teal"
SMALL_TEAL_FILE="$TEMPDIR/sm-app.teal"
+APPR_PROG="$TEMPDIR/appr-prog.teal"
+BIG_APPR_PROG="$TEMPDIR/big-appr-prog.teal"
generate_teal "$BIG_TEAL_FILE" 3 4090 1 "int 0\nbalance\npop\n"
generate_teal "$BIG_TEAL_V4_FILE" 4 4090 1 "int 0\nbalance\npop\n"
generate_teal "$SMALL_TEAL_FILE" 3 10 1 "int 0\nbalance\npop\n"
+generate_teal "$APPR_PROG" 4 3072 1 "int 0\nbalance\npop\n"
+generate_teal "$BIG_APPR_PROG" 4 4098 1 "int 0\nbalance\npop\n"
# App create fails. Approval program too long
RES=$(${gcmd} app create --creator ${ACCOUNT} --approval-prog "${BIG_TEAL_FILE}" --clear-prog "${BIG_TEAL_FILE}" --global-byteslices 1 --global-ints 0 --local-byteslices 0 --local-ints 0 2>&1 || true)
@@ -79,5 +83,34 @@ if [[ $RES != *"${EXPERROR}"* ]]; then
false
fi
-# App create with extra pages, succeedd
+# App create with extra pages, succeeded
${gcmd} app create --creator ${ACCOUNT} --approval-prog "${SMALL_TEAL_FILE}" --clear-prog "${SMALL_TEAL_FILE}" --extra-pages 1 --global-byteslices 1 --global-ints 0 --local-byteslices 0 --local-ints 0
+
+# App update
+RES=$(${gcmd} app create --creator ${ACCOUNT} --approval-prog "${SMALL_TEAL_FILE}" --clear-prog "${SMALL_TEAL_FILE}" --extra-pages 1 --global-byteslices 1 --global-ints 0 --local-byteslices 0 --local-ints 0 2>&1 || true)
+EXP="Created app"
+APPID=$(echo $RES | awk '{print $NF}')
+if [[ $RES != *"${EXP}"* ]]; then
+ date '+app-extra-pages-test FAIL the application creation should pass %Y%m%d_%H%M%S'
+ false
+fi
+
+RES=$(${gcmd} app info --app-id ${APPID} 2>&1 || true)
+PROGHASH="Approval hash: 7356635AKR4FJOOKXXBWNN6HDJ5U3O2YWAOSK6NZBPMOGIQSWCL2N74VT4"
+if [[ $RES != *"${PROGHASH}"* ]]; then
+ date '+app-extra-pages-test FAIL the application info should succeed %Y%m%d_%H%M%S'
+ false
+fi
+
+RES=$(${gcmd} app update --app-id ${APPID} --approval-prog "${APPR_PROG}" --clear-prog "${SMALL_TEAL_FILE}" --from ${ACCOUNT} 2>&1 || true)
+EXP="Attempting to update app"
+if [[ $RES != *"${EXP}"* ]]; then
+ date '+app-extra-pages-test FAIL the application update should succeed %Y%m%d_%H%M%S'
+ false
+fi
+
+RES=$(${gcmd} app info --app-id ${APPID} 2>&1 || true)
+if [[ $RES == *"${PROGHASH}"* ]]; then
+ date '+app-extra-pages-test FAIL the application approval program should have been updated %Y%m%d_%H%M%S'
+ false
+fi