Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions config/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -987,6 +989,9 @@ func initConsensusProtocols() {
// Enable TEAL 5 / AVM 1.0
vFuture.LogicSigVersion = 5

// Enable ExtraProgramPages for application update
vFuture.EnableExtraPagesOnAppUpdate = true

Consensus[protocol.ConsensusFuture] = vFuture
}

Expand Down
8 changes: 7 additions & 1 deletion data/transactions/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,7 @@ func (tx Transaction) WellFormed(spec SpecialAddresses, proto config.ConsensusPa
}
}

effectiveEPP := tx.ExtraProgramPages
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

call it maxExtraProgramPages ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think effectiveEPP is better because it doesn't always equal to maxExtraProgramPages. when ApplicationID=0, effectiveEPP = tx.ExtraProgramPages

// Schemas and ExtraProgramPages may only be set during application creation
if tx.ApplicationID != 0 {
if tx.LocalStateSchema != (basics.StateSchema{}) ||
Expand All @@ -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
Expand Down Expand Up @@ -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)
}
Expand Down
49 changes: 49 additions & 0 deletions data/transactions/transaction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,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{
Expand Down Expand Up @@ -458,6 +459,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)
Expand Down
10 changes: 10 additions & 0 deletions ledger/apply/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,16 @@ 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 {
allowed := int(1+params.ExtraProgramPages) * proto.MaxAppProgramLen
actual := len(ac.ApprovalProgram) + len(ac.ClearStateProgram)
if actual > allowed {
return fmt.Errorf("updateApplication app programs too long, %d. max total len %d bytes", actual, allowed)
}
}

params.ApprovalProgram = ac.ApprovalProgram
params.ClearStateProgram = ac.ClearStateProgram

Expand Down
64 changes: 63 additions & 1 deletion ledger/apply/application_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -919,6 +919,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) {
Expand Down Expand Up @@ -960,7 +961,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

Expand All @@ -987,6 +988,67 @@ func TestAppCallApplyUpdate(t *testing.T) {
a.Equal([]byte{2}, br.AppParams[appIdx].ApprovalProgram)
a.Equal([]byte{2}, br.AppParams[appIdx].ClearStateProgram)
a.Equal(basics.EvalDelta{}, ad.EvalDelta)

// check app program len
appr := make([]byte, 6050)

for i := range appr {
appr[i] = 2
}
appr[0] = 4
ac = transactions.ApplicationCallTxnFields{
ApplicationID: appIdx,
OnCompletion: transactions.UpdateApplicationOC,
ApprovalProgram: appr,
ClearStateProgram: []byte{2},
}
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 program len check happens in future consensus proto version
b.SetProto(protocol.ConsensusFuture)
proto = b.ConsensusParams()
ep.Proto = &proto

b.pass = true
err = ApplicationCall(ac, h, &b, ad, &ep, txnCounter)
a.Contains(err.Error(), "updateApplication app programs too long")

// check extraProgramPages is used
appr = make([]byte, 3072)

for i := range appr {
appr[i] = 2
}
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)

}

func TestAppCallApplyDelete(t *testing.T) {
Expand Down
35 changes: 34 additions & 1 deletion test/scripts/e2e_subs/e2e-app-extra-pages.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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