summaryrefslogtreecommitdiff
path: root/data/transactions/logic/eval_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'data/transactions/logic/eval_test.go')
-rw-r--r--data/transactions/logic/eval_test.go1713
1 files changed, 617 insertions, 1096 deletions
diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go
index 72c07f01f..3aea31b1b 100644
--- a/data/transactions/logic/eval_test.go
+++ b/data/transactions/logic/eval_test.go
@@ -25,6 +25,7 @@ import (
"strings"
"testing"
+ "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/algorand/go-algorand/config"
@@ -32,20 +33,19 @@ import (
"github.com/algorand/go-algorand/data/basics"
"github.com/algorand/go-algorand/data/bookkeeping"
"github.com/algorand/go-algorand/data/transactions"
- "github.com/algorand/go-algorand/data/transactions/logictest"
"github.com/algorand/go-algorand/logging"
"github.com/algorand/go-algorand/protocol"
"github.com/algorand/go-algorand/test/partitiontest"
)
-// Note that most of the tests use defaultEvalProto/defaultEvalParams as evaluator version so that
+// Note that most of the tests use makeTestProto/defaultEvalParams as evaluator version so that
// we check that TEAL v1 and v2 programs are compatible with the latest evaluator
-func defaultEvalProto() config.ConsensusParams {
- return defaultEvalProtoWithVersion(LogicVersion)
+func makeTestProto() *config.ConsensusParams {
+ return makeTestProtoV(LogicVersion)
}
-func defaultEvalProtoWithVersion(version uint64) config.ConsensusParams {
- return config.ConsensusParams{
+func makeTestProtoV(version uint64) *config.ConsensusParams {
+ return &config.ConsensusParams{
LogicSigVersion: version,
LogicSigMaxCost: 20000,
Application: version >= appsEnabledVersion,
@@ -79,45 +79,75 @@ func defaultEvalProtoWithVersion(version uint64) config.ConsensusParams {
EnableFeePooling: true,
// Chosen to be different from one another and from normal proto
- MaxAppTxnAccounts: 3,
- MaxAppTxnForeignApps: 5,
- MaxAppTxnForeignAssets: 6,
- }
-}
+ MaxAppTxnAccounts: 3,
+ MaxAppTxnForeignApps: 5,
+ MaxAppTxnForeignAssets: 6,
+ MaxAppTotalTxnReferences: 7,
+
+ MaxAppArgs: 12,
+ MaxAppTotalArgLen: 800,
+
+ MaxAppProgramLen: 900,
+ MaxAppTotalProgramLen: 1200, // Weird, but better tests
+ MaxExtraAppProgramPages: 2,
-func defaultEvalParamsV1(sb *strings.Builder, txn *transactions.SignedTxn) EvalParams {
- return defaultEvalParamsWithVersion(sb, txn, 1)
+ MaxGlobalSchemaEntries: 30,
+ MaxLocalSchemaEntries: 13,
+
+ EnableAppCostPooling: true,
+ EnableInnerTransactionPooling: true,
+ }
}
-func defaultEvalParams(sb *strings.Builder, txn *transactions.SignedTxn) EvalParams {
- return defaultEvalParamsWithVersion(sb, txn, LogicVersion)
+func defaultEvalParams(txn *transactions.SignedTxn) *EvalParams {
+ return defaultEvalParamsWithVersion(txn, LogicVersion)
}
-func benchmarkEvalParams(sb *strings.Builder, txn *transactions.SignedTxn) EvalParams {
- ep := defaultEvalParamsWithVersion(sb, txn, LogicVersion)
- ep.Proto.LogicSigMaxCost = 1000 * 1000
+func benchmarkEvalParams(txn *transactions.SignedTxn) *EvalParams {
+ ep := defaultEvalParamsWithVersion(txn, LogicVersion)
+ ep.Trace = nil // Tracing would slow down benchmarks
+ clone := *ep.Proto
+ bigBudget := uint64(1000 * 1000) // Allow long run times
+ clone.LogicSigMaxCost = bigBudget
+ clone.MaxAppProgramCost = int(bigBudget)
+ ep.Proto = &clone
+ ep.PooledApplicationBudget = &bigBudget
return ep
}
-func defaultEvalParamsWithVersion(sb *strings.Builder, txn *transactions.SignedTxn, version uint64) EvalParams {
- proto := defaultEvalProtoWithVersion(version)
-
- var pt *transactions.SignedTxn
+func defaultEvalParamsWithVersion(txn *transactions.SignedTxn, version uint64) *EvalParams {
+ ep := &EvalParams{
+ Proto: makeTestProtoV(version),
+ TxnGroup: make([]transactions.SignedTxnWithAD, 1),
+ Specials: &transactions.SpecialAddresses{},
+ Trace: &strings.Builder{},
+ }
if txn != nil {
- pt = txn
- } else {
- pt = &transactions.SignedTxn{}
+ ep.TxnGroup[0].SignedTxn = *txn
}
+ ep.reset()
+ return ep
+}
- ep := EvalParams{}
- ep.Proto = &proto
- ep.Txn = pt
- ep.PastSideEffects = MakePastSideEffects(5)
- ep.Specials = &transactions.SpecialAddresses{}
- if sb != nil { // have to do this since go's nil semantics: https://golang.org/doc/faq#nil_error
- ep.Trace = sb
+// reset puts an ep back into its original state. This is in *_test.go because
+// no real code should ever need this. EvalParams should be created to evaluate
+// a group, and then thrown away.
+func (ep *EvalParams) reset() {
+ if ep.Proto.EnableAppCostPooling {
+ budget := uint64(ep.Proto.MaxAppProgramCost)
+ ep.PooledApplicationBudget = &budget
}
- return ep
+ if ep.Proto.EnableInnerTransactionPooling {
+ inners := ep.Proto.MaxTxGroupSize * ep.Proto.MaxInnerTransactions
+ ep.pooledAllowedInners = &inners
+ }
+ ep.pastScratch = make([]*scratchSpace, ep.Proto.MaxTxGroupSize)
+ for i := range ep.TxnGroup {
+ ep.TxnGroup[i].ApplyData = transactions.ApplyData{}
+ }
+ ep.created = &resources{}
+ ep.appAddrCache = make(map[basics.AppIndex]basics.Address)
+ ep.Trace = &strings.Builder{}
}
func TestTooManyArgs(t *testing.T) {
@@ -131,8 +161,7 @@ func TestTooManyArgs(t *testing.T) {
txn.Lsig.Logic = ops.Program
args := [transactions.EvalMaxArgs + 1][]byte{}
txn.Lsig.Args = args[:]
- sb := strings.Builder{}
- pass, err := Eval(ops.Program, defaultEvalParams(&sb, &txn))
+ pass, err := EvalSignature(0, defaultEvalParams(&txn))
require.Error(t, err)
require.False(t, pass)
})
@@ -143,32 +172,23 @@ func TestEmptyProgram(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()
- pass, err := Eval(nil, defaultEvalParams(nil, nil))
- require.Error(t, err)
- require.Contains(t, err.Error(), "invalid program (empty)")
- require.False(t, pass)
+ testLogicBytes(t, nil, defaultEvalParams(nil), "invalid", "invalid program (empty)")
}
// TestMinTealVersionParamEval tests eval/check reading the MinTealVersion from the param
-func TestMinTealVersionParamEvalCheck(t *testing.T) {
+func TestMinTealVersionParamEvalCheckSignature(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()
- params := defaultEvalParams(nil, nil)
+ params := defaultEvalParams(nil)
version2 := uint64(rekeyingEnabledVersion)
params.MinTealVersion = &version2
program := make([]byte, binary.MaxVarintLen64)
// set the teal program version to 1
binary.PutUvarint(program, 1)
- err := Check(program, params)
- require.Contains(t, err.Error(), fmt.Sprintf("program version must be >= %d", appsEnabledVersion))
-
- // If the param is read correctly, the eval should fail
- pass, err := Eval(program, params)
- require.Error(t, err)
- require.Contains(t, err.Error(), fmt.Sprintf("program version must be >= %d", appsEnabledVersion))
- require.False(t, pass)
+ verErr := fmt.Sprintf("program version must be >= %d", appsEnabledVersion)
+ testAppBytes(t, program, params, verErr, verErr)
}
func TestTxnFieldToTealValue(t *testing.T) {
@@ -225,20 +245,8 @@ func TestWrongProtoVersion(t *testing.T) {
for v := uint64(1); v <= AssemblerMaxVersion; v++ {
t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) {
ops := testProg(t, "int 1", v)
- var txn transactions.SignedTxn
- txn.Lsig.Logic = ops.Program
- sb := strings.Builder{}
- proto := defaultEvalProto()
- proto.LogicSigVersion = 0
- ep := defaultEvalParams(&sb, &txn)
- ep.Proto = &proto
- err := Check(ops.Program, ep)
- require.Error(t, err)
- require.Contains(t, err.Error(), "LogicSig not supported")
- pass, err := Eval(ops.Program, ep)
- require.Error(t, err)
- require.Contains(t, err.Error(), "LogicSig not supported")
- require.False(t, pass)
+ ep := defaultEvalParamsWithVersion(nil, 0)
+ testAppBytes(t, ops.Program, ep, "LogicSig not supported", "LogicSig not supported")
})
}
}
@@ -268,11 +276,10 @@ byte base64 5rZMNsevs5sULO+54aN+OvU6lQ503z2X+SSYUABIx7E=
var txn transactions.SignedTxn
txn.Lsig.Logic = ops.Program
txn.Lsig.Args = [][]byte{[]byte("=0\x97S\x85H\xe9\x91B\xfd\xdb;1\xf5Z\xaec?\xae\xf2I\x93\x08\x12\x94\xaa~\x06\x08\x849b")}
- sb := strings.Builder{}
- ep := defaultEvalParams(&sb, &txn)
- err := Check(ops.Program, ep)
+ ep := defaultEvalParams(&txn)
+ err := CheckSignature(0, ep)
require.NoError(t, err)
- pass, err := Eval(ops.Program, ep)
+ pass, err := EvalSignature(0, ep)
require.True(t, pass)
require.NoError(t, err)
})
@@ -329,70 +336,65 @@ func TestTLHC(t *testing.T) {
// right answer
txn.Lsig.Args = [][]byte{secret}
txn.Txn.FirstValid = 999999
- sb := strings.Builder{}
block := bookkeeping.Block{}
- ep := defaultEvalParams(&sb, &txn)
- err := Check(ops.Program, ep)
+ ep := defaultEvalParams(&txn)
+ err := CheckSignature(0, ep)
if err != nil {
t.Log(hex.EncodeToString(ops.Program))
- t.Log(sb.String())
+ t.Log(ep.Trace.String())
}
require.NoError(t, err)
- pass, err := Eval(ops.Program, ep)
+ pass, err := EvalSignature(0, ep)
if pass {
t.Log(hex.EncodeToString(ops.Program))
- t.Log(sb.String())
+ t.Log(ep.Trace.String())
}
require.False(t, pass)
isNotPanic(t, err)
txn.Txn.Receiver = a2
txn.Txn.CloseRemainderTo = a2
- sb = strings.Builder{}
- ep = defaultEvalParams(&sb, &txn)
- pass, err = Eval(ops.Program, ep)
+ ep = defaultEvalParams(&txn)
+ pass, err = EvalSignature(0, ep)
if !pass {
t.Log(hex.EncodeToString(ops.Program))
- t.Log(sb.String())
+ t.Log(ep.Trace.String())
}
require.True(t, pass)
require.NoError(t, err)
txn.Txn.Receiver = a2
txn.Txn.CloseRemainderTo = a2
- sb = strings.Builder{}
txn.Txn.FirstValid = 1
- ep = defaultEvalParams(&sb, &txn)
- pass, err = Eval(ops.Program, ep)
+ ep = defaultEvalParams(&txn)
+ pass, err = EvalSignature(0, ep)
if pass {
t.Log(hex.EncodeToString(ops.Program))
- t.Log(sb.String())
+ t.Log(ep.Trace.String())
}
require.False(t, pass)
isNotPanic(t, err)
txn.Txn.Receiver = a1
txn.Txn.CloseRemainderTo = a1
- sb = strings.Builder{}
txn.Txn.FirstValid = 999999
- ep = defaultEvalParams(&sb, &txn)
- pass, err = Eval(ops.Program, ep)
+ ep = defaultEvalParams(&txn)
+ pass, err = EvalSignature(0, ep)
if !pass {
t.Log(hex.EncodeToString(ops.Program))
- t.Log(sb.String())
+ t.Log(ep.Trace.String())
}
require.True(t, pass)
require.NoError(t, err)
// wrong answer
txn.Lsig.Args = [][]byte{[]byte("=0\x97S\x85H\xe9\x91B\xfd\xdb;1\xf5Z\xaec?\xae\xf2I\x93\x08\x12\x94\xaa~\x06\x08\x849a")}
- sb = strings.Builder{}
block.BlockHeader.Round = 1
- ep = defaultEvalParams(&sb, &txn)
- pass, err = Eval(ops.Program, ep)
+ ep = defaultEvalParams(&txn)
+ pass, err = EvalSignature(0, ep)
if pass {
t.Log(hex.EncodeToString(ops.Program))
- t.Log(sb.String())
+ t.Log(ep.Trace.String())
}
require.False(t, pass)
isNotPanic(t, err)
@@ -790,20 +792,8 @@ func TestTxnBadField(t *testing.T) {
t.Parallel()
program := []byte{0x01, 0x31, 0x7f}
- err := Check(program, defaultEvalParams(nil, nil))
- require.NoError(t, err) // TODO: Check should know the type stack was wrong
- sb := strings.Builder{}
- var txn transactions.SignedTxn
- txn.Lsig.Logic = program
- txn.Lsig.Args = nil
- pass, err := Eval(program, defaultEvalParams(&sb, &txn))
- if pass {
- t.Log(hex.EncodeToString(program))
- t.Log(sb.String())
- }
- require.Error(t, err)
- require.False(t, pass)
- isNotPanic(t, err)
+ testLogicBytes(t, program, defaultEvalParams(nil), "invalid txn field")
+ // TODO: Check should know the type stack was wrong
// test txn does not accept ApplicationArgs and Accounts
txnOpcode := OpsByName[LogicVersion]["txn"].Opcode
@@ -815,10 +805,7 @@ func TestTxnBadField(t *testing.T) {
ops := testProg(t, source, AssemblerMaxVersion)
require.Equal(t, txnaOpcode, ops.Program[1])
ops.Program[1] = txnOpcode
- pass, err = Eval(ops.Program, defaultEvalParams(&sb, &txn))
- require.Error(t, err)
- require.Contains(t, err.Error(), fmt.Sprintf("invalid txn field %d", field))
- require.False(t, pass)
+ testLogicBytes(t, ops.Program, defaultEvalParams(nil), fmt.Sprintf("invalid txn field %d", field))
}
}
@@ -827,49 +814,16 @@ func TestGtxnBadIndex(t *testing.T) {
t.Parallel()
program := []byte{0x01, 0x33, 0x1, 0x01}
- err := Check(program, defaultEvalParams(nil, nil))
- require.NoError(t, err) // TODO: Check should know the type stack was wrong
- sb := strings.Builder{}
- var txn transactions.SignedTxn
- txn.Lsig.Logic = program
- txn.Lsig.Args = nil
- txgroup := make([]transactions.SignedTxn, 1)
- txgroup[0] = txn
- ep := defaultEvalParams(&sb, &txn)
- ep.TxnGroup = txgroup
- pass, err := Eval(program, ep)
- if pass {
- t.Log(hex.EncodeToString(program))
- t.Log(sb.String())
- }
- require.Error(t, err)
- require.False(t, pass)
- isNotPanic(t, err)
+ testLogicBytes(t, program, defaultEvalParams(nil), "gtxn lookup")
}
func TestGtxnBadField(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()
- program := []byte{0x01, 0x33, 0x0, 0x7f}
- err := Check(program, defaultEvalParams(nil, nil))
- require.NoError(t, err) // TODO: Check should know the type stack was wrong
- sb := strings.Builder{}
- var txn transactions.SignedTxn
- txn.Lsig.Logic = program
- txn.Lsig.Args = nil
- txgroup := make([]transactions.SignedTxn, 1)
- txgroup[0] = txn
- ep := defaultEvalParams(&sb, &txn)
- ep.TxnGroup = txgroup
- pass, err := Eval(program, ep)
- if pass {
- t.Log(hex.EncodeToString(program))
- t.Log(sb.String())
- }
- require.Error(t, err)
- require.False(t, pass)
- isNotPanic(t, err)
+ program := []byte{0x01, 0x33, 0x0, 127}
+ // TODO: Check should know the type stack was wrong
+ testLogicBytes(t, program, defaultEvalParams(nil), "invalid txn field 127")
// test gtxn does not accept ApplicationArgs and Accounts
txnOpcode := OpsByName[LogicVersion]["txn"].Opcode
@@ -881,10 +835,7 @@ func TestGtxnBadField(t *testing.T) {
ops := testProg(t, source, AssemblerMaxVersion)
require.Equal(t, txnaOpcode, ops.Program[1])
ops.Program[1] = txnOpcode
- pass, err = Eval(ops.Program, defaultEvalParams(&sb, &txn))
- require.Error(t, err)
- require.Contains(t, err.Error(), fmt.Sprintf("invalid txn field %d", field))
- require.False(t, pass)
+ testLogicBytes(t, ops.Program, defaultEvalParams(nil), fmt.Sprintf("invalid txn field %d", field))
}
}
@@ -892,21 +843,8 @@ func TestGlobalBadField(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()
- program := []byte{0x01, 0x32, 0x7f}
- err := Check(program, defaultEvalParams(nil, nil))
- require.NoError(t, err) // Check does not validates opcode args
- sb := strings.Builder{}
- var txn transactions.SignedTxn
- txn.Lsig.Logic = program
- txn.Lsig.Args = nil
- pass, err := Eval(program, defaultEvalParams(&sb, &txn))
- if pass {
- t.Log(hex.EncodeToString(program))
- t.Log(sb.String())
- }
- require.Error(t, err)
- require.False(t, pass)
- isNotPanic(t, err)
+ program := []byte{0x01, 0x32, 127}
+ testLogicBytes(t, program, defaultEvalParams(nil), "invalid global field")
}
func TestArg(t *testing.T) {
@@ -919,11 +857,8 @@ func TestArg(t *testing.T) {
if v >= 5 {
source += "int 0; args; int 1; args; ==; assert; int 2; args; int 3; args; !=; assert"
}
- ops := testProg(t, source, v)
- err := Check(ops.Program, defaultEvalParams(nil, nil))
- require.NoError(t, err)
+
var txn transactions.SignedTxn
- txn.Lsig.Logic = ops.Program
txn.Lsig.Args = [][]byte{
[]byte("aoeu"),
[]byte("aoeu"),
@@ -931,28 +866,22 @@ func TestArg(t *testing.T) {
[]byte("aoeu3"),
[]byte("aoeu4"),
}
- sb := strings.Builder{}
- pass, err := Eval(ops.Program, defaultEvalParams(&sb, &txn))
- if !pass {
- t.Log(hex.EncodeToString(ops.Program))
- t.Log(sb.String())
- }
- require.NoError(t, err)
- require.True(t, pass)
+ ops := testProg(t, source, v)
+ testLogicBytes(t, ops.Program, defaultEvalParams(&txn))
})
}
}
const globalV1TestProgram = `
global MinTxnFee
-int 123
+int 1001
==
global MinBalance
-int 1000000
+int 1001
==
&&
global MaxTxnLife
-int 999
+int 1500
==
&&
global ZeroAddress
@@ -981,7 +910,7 @@ int 0
>
&&
global CurrentApplicationID
-int 42
+int 888
==
&&
`
@@ -1009,7 +938,17 @@ byte 0x0706000000000000000000000000000000000000000000000000000000000000
`
const globalV6TestProgram = globalV5TestProgram + `
-// No new globals in v6
+global OpcodeBudget
+int 0
+>
+&&
+global CallerApplicationAddress
+global ZeroAddress
+==
+&&
+global CallerApplicationID
+!
+&&
`
func TestGlobal(t *testing.T) {
@@ -1019,81 +958,43 @@ func TestGlobal(t *testing.T) {
type desc struct {
lastField GlobalField
program string
- eval func([]byte, EvalParams) (bool, error)
- check func([]byte, EvalParams) error
}
+ // Associate the highest allowed global constant with each version's test program
tests := map[uint64]desc{
- 0: {GroupSize, globalV1TestProgram, Eval, Check},
- 1: {GroupSize, globalV1TestProgram, Eval, Check},
- 2: {
- CurrentApplicationID, globalV2TestProgram,
- EvalStateful, CheckStateful,
- },
- 3: {
- CreatorAddress, globalV3TestProgram,
- EvalStateful, CheckStateful,
- },
- 4: {
- CreatorAddress, globalV4TestProgram,
- EvalStateful, CheckStateful,
- },
- 5: {
- GroupID, globalV5TestProgram,
- EvalStateful, CheckStateful,
- },
- 6: {
- GroupID, globalV6TestProgram,
- EvalStateful, CheckStateful,
- },
+ 0: {GroupSize, globalV1TestProgram},
+ 1: {GroupSize, globalV1TestProgram},
+ 2: {CurrentApplicationID, globalV2TestProgram},
+ 3: {CreatorAddress, globalV3TestProgram},
+ 4: {CreatorAddress, globalV4TestProgram},
+ 5: {GroupID, globalV5TestProgram},
+ 6: {CallerApplicationAddress, globalV6TestProgram},
}
// tests keys are versions so they must be in a range 1..AssemblerMaxVersion plus zero version
require.LessOrEqual(t, len(tests), AssemblerMaxVersion+1)
+ require.Len(t, globalFieldSpecs, int(invalidGlobalField))
- ledger := logictest.MakeLedger(nil)
+ ledger := MakeLedger(nil)
addr, err := basics.UnmarshalChecksumAddress(testAddr)
require.NoError(t, err)
- ledger.NewApp(addr, basics.AppIndex(42), basics.AppParams{})
+ ledger.NewApp(addr, 888, basics.AppParams{})
for v := uint64(1); v <= AssemblerMaxVersion; v++ {
_, ok := tests[v]
require.True(t, ok)
t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) {
last := tests[v].lastField
testProgram := tests[v].program
- check := tests[v].check
- eval := tests[v].eval
- for _, globalField := range GlobalFieldNames[:last] {
+ for _, globalField := range GlobalFieldNames[:last+1] {
if !strings.Contains(testProgram, globalField) {
t.Errorf("TestGlobal missing field %v", globalField)
}
}
- ops := testProg(t, testProgram, v)
- err := check(ops.Program, defaultEvalParams(nil, nil))
- require.NoError(t, err)
- var txn transactions.SignedTxn
- txn.Lsig.Logic = ops.Program
+
+ txn := transactions.SignedTxn{}
txn.Txn.Group = crypto.Digest{0x07, 0x06}
- txgroup := make([]transactions.SignedTxn, 1)
- txgroup[0] = txn
- sb := strings.Builder{}
- proto := config.ConsensusParams{
- MinTxnFee: 123,
- MinBalance: 1000000,
- MaxTxnLife: 999,
- LogicSigVersion: LogicVersion,
- LogicSigMaxCost: 20000,
- MaxAppProgramCost: 700,
- }
- ep := defaultEvalParams(&sb, &txn)
- ep.TxnGroup = txgroup
- ep.Proto = &proto
+
+ ep := defaultEvalParams(&txn)
ep.Ledger = ledger
- pass, err := eval(ops.Program, ep)
- if !pass {
- t.Log(hex.EncodeToString(ops.Program))
- t.Log(sb.String())
- }
- require.NoError(t, err)
- require.True(t, pass)
+ testApp(t, tests[v].program, ep)
})
}
}
@@ -1134,19 +1035,14 @@ int %s
==
&&`, symbol, string(tt))
ops := testProg(t, text, v)
- err := Check(ops.Program, defaultEvalParams(nil, nil))
- require.NoError(t, err)
- var txn transactions.SignedTxn
+ txn := transactions.SignedTxn{}
txn.Txn.Type = tt
- sb := strings.Builder{}
- ep := defaultEvalParams(&sb, &txn)
- pass, err := Eval(ops.Program, ep)
- if !pass {
- t.Log(hex.EncodeToString(ops.Program))
- t.Log(sb.String())
+ if v < appsEnabledVersion && tt == protocol.ApplicationCallTx {
+ testLogicBytes(t, ops.Program, defaultEvalParams(&txn),
+ "program version must be", "program version must be")
+ return
}
- require.NoError(t, err)
- require.True(t, pass)
+ testLogicBytes(t, ops.Program, defaultEvalParams(&txn))
})
}
})
@@ -1283,7 +1179,7 @@ arg 8
`
const testTxnProgramTextV2 = testTxnProgramTextV1 + `txn ApplicationID
-int 123
+int 888
==
&&
txn OnCompletion
@@ -1459,6 +1355,26 @@ int 1
const testTxnProgramTextV6 = testTxnProgramTextV5 + `
assert
+txn CreatedAssetID
+int 0
+==
+assert
+
+txn CreatedApplicationID
+int 0
+==
+assert
+
+txn NumLogs
+int 2
+==
+assert
+
+txn Logs 1
+byte "prefilled"
+==
+assert
+
int 1
`
@@ -1486,7 +1402,7 @@ func makeSampleTxn() transactions.SignedTxn {
txn.Txn.AssetSender = txn.Txn.Receiver
txn.Txn.AssetReceiver = txn.Txn.CloseRemainderTo
txn.Txn.AssetCloseTo = txn.Txn.Sender
- txn.Txn.ApplicationID = basics.AppIndex(123)
+ txn.Txn.ApplicationID = basics.AppIndex(888)
txn.Txn.Accounts = make([]basics.Address, 1)
txn.Txn.Accounts[0] = txn.Txn.Receiver
rekeyToAddr := []byte("aoeuiaoeuiaoeuiaoeuiaoeuiaoeui05")
@@ -1530,32 +1446,31 @@ func makeSampleTxn() transactions.SignedTxn {
return txn
}
-func makeSampleTxnGroup(txn transactions.SignedTxn) []transactions.SignedTxn {
- txgroup := make([]transactions.SignedTxn, 2)
- txgroup[0] = txn
- txgroup[1].Txn.Amount.Raw = 42
- txgroup[1].Txn.Fee.Raw = 1066
- txgroup[1].Txn.FirstValid = 42
- txgroup[1].Txn.LastValid = 1066
- txgroup[1].Txn.Sender = txn.Txn.Receiver
- txgroup[1].Txn.Receiver = txn.Txn.Sender
- txgroup[1].Txn.ExtraProgramPages = 2
- return txgroup
+// makeSampleTxnGroup creates a sample txn group. If less than two transactions
+// are supplied, samples are used.
+func makeSampleTxnGroup(txns ...transactions.SignedTxn) []transactions.SignedTxn {
+ if len(txns) == 0 {
+ txns = []transactions.SignedTxn{makeSampleTxn()}
+ }
+ if len(txns) == 1 {
+ second := transactions.SignedTxn{}
+ second.Txn.Type = protocol.PaymentTx
+ second.Txn.Amount.Raw = 42
+ second.Txn.Fee.Raw = 1066
+ second.Txn.FirstValid = 42
+ second.Txn.LastValid = 1066
+ second.Txn.Sender = txns[0].Txn.Receiver
+ second.Txn.Receiver = txns[0].Txn.Sender
+ second.Txn.ExtraProgramPages = 2
+ txns = append(txns, second)
+ }
+ return txns
}
func TestTxn(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()
- for i, txnField := range TxnFieldNames {
- fs := txnFieldSpecByField[TxnField(i)]
- if !fs.effects && !strings.Contains(testTxnProgramTextV6, txnField) {
- if txnField != FirstValidTime.String() {
- t.Errorf("TestTxn missing field %v", txnField)
- }
- }
- }
-
tests := map[uint64]string{
1: testTxnProgramTextV1,
2: testTxnProgramTextV2,
@@ -1565,13 +1480,31 @@ func TestTxn(t *testing.T) {
6: testTxnProgramTextV6,
}
+ for i, txnField := range TxnFieldNames {
+ fs := txnFieldSpecByField[TxnField(i)]
+ // Ensure that each field appears, starting in the version it was introduced
+ for v := uint64(1); v <= uint64(LogicVersion); v++ {
+ if v < fs.version {
+ continue
+ }
+ if !strings.Contains(tests[v], txnField) {
+ if txnField == FirstValidTime.String() {
+ continue
+ }
+ // fields were introduced for itxn before they became available for txn
+ if v < txnEffectsVersion && fs.effects {
+ continue
+ }
+ t.Errorf("testTxnProgramTextV%d missing field %v", v, txnField)
+ }
+ }
+ }
+
clearOps := testProg(t, "int 1", 1)
for v, source := range tests {
t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) {
ops := testProg(t, source, v)
- err := Check(ops.Program, defaultEvalParams(nil, nil))
- require.NoError(t, err)
txn := makeSampleTxn()
txn.Txn.ApprovalProgram = ops.Program
txn.Txn.ClearStateProgram = clearOps.Program
@@ -1597,17 +1530,28 @@ func TestTxn(t *testing.T) {
programHash[:],
clearProgramHash[:],
}
- sb := strings.Builder{}
- ep := defaultEvalParams(&sb, &txn)
- ep.Ledger = logictest.MakeLedger(nil)
- ep.GroupIndex = 3
- pass, err := Eval(ops.Program, ep)
- if !pass {
- t.Log(hex.EncodeToString(ops.Program))
- t.Log(sb.String())
+ // Since we test GroupIndex ==3, we need to fake up such a group
+ ep := defaultEvalParams(nil)
+ ep.TxnGroup = transactions.WrapSignedTxnsWithAD([]transactions.SignedTxn{txn, txn, txn, txn})
+ ep.TxnGroup[3].EvalDelta.Logs = []string{"x", "prefilled"}
+ if v < txnEffectsVersion {
+ testLogicFull(t, ops.Program, 3, ep)
+ } else {
+ // Starting in txnEffectsVersion we can't access all fields in Logic mode
+ testLogicFull(t, ops.Program, 3, ep, "not allowed in current mode")
+ // And the early tests use "arg" a lot - not allowed in stateful. So remove those tests.
+ lastArg := strings.Index(source, "arg 10\n==\n&&")
+ require.NotEqual(t, -1, lastArg)
+
+ appSafe := "int 1" + strings.Replace(source[lastArg+12:], `txn Sender
+int 0
+args
+==
+assert`, "", 1)
+
+ ops := testProg(t, appSafe, v)
+ testAppFull(t, ops.Program, 3, basics.AppIndex(888), ep)
}
- require.NoError(t, err)
- require.True(t, pass)
})
}
}
@@ -1656,44 +1600,22 @@ int 0
return
`
ops := testProg(t, cachedTxnProg, 2)
- sb := strings.Builder{}
- err := Check(ops.Program, defaultEvalParams(&sb, nil))
- if err != nil {
- t.Log(hex.EncodeToString(ops.Program))
- t.Log(sb.String())
- }
- require.NoError(t, err)
- txn := makeSampleTxn()
- txgroup := makeSampleTxnGroup(txn)
- txn.Lsig.Logic = ops.Program
- txid0 := txgroup[0].ID()
- txid1 := txgroup[1].ID()
- txn.Lsig.Args = [][]byte{
+
+ ep, _, _ := makeSampleEnv()
+ txid0 := ep.TxnGroup[0].ID()
+ txid1 := ep.TxnGroup[1].ID()
+ ep.TxnGroup[0].Lsig.Args = [][]byte{
txid0[:],
txid1[:],
}
- sb = strings.Builder{}
- ep := defaultEvalParams(&sb, &txn)
- ep.TxnGroup = txgroup
- pass, err := Eval(ops.Program, ep)
- if !pass || err != nil {
- t.Log(hex.EncodeToString(ops.Program))
- t.Log(sb.String())
- }
- require.NoError(t, err)
- require.True(t, pass)
+ testLogicBytes(t, ops.Program, ep)
}
func TestGaid(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()
- checkCreatableIDProg := `
-gaid 0
-int 100
-==
-`
- ops := testProg(t, checkCreatableIDProg, 4)
+ check0 := testProg(t, "gaid 0; int 100; ==", 4)
txn := makeSampleTxn()
txn.Txn.Type = protocol.ApplicationCallTx
txgroup := make([]transactions.SignedTxn, 3)
@@ -1701,53 +1623,40 @@ int 100
targetTxn := makeSampleTxn()
targetTxn.Txn.Type = protocol.AssetConfigTx
txgroup[0] = targetTxn
- sb := strings.Builder{}
- ledger := logictest.MakeLedger(nil)
- ledger.SetTrackedCreatable(0, basics.CreatableLocator{Index: 100})
- ep := defaultEvalParams(&sb, &txn)
- ep.Ledger = ledger
- ep.TxnGroup = txgroup
- ep.GroupIndex = 1
- pass, err := EvalStateful(ops.Program, ep)
+ ep := defaultEvalParams(nil)
+ ep.TxnGroup = transactions.WrapSignedTxnsWithAD(txgroup)
+ ep.Ledger = MakeLedger(nil)
+
+ // should fail when no creatable was created
+ _, err := EvalApp(check0.Program, 1, 0, ep)
+ require.Error(t, err)
+ require.Contains(t, err.Error(), "the txn did not create anything")
+
+ ep.TxnGroup[0].ApplyData.ConfigAsset = 100
+ pass, err := EvalApp(check0.Program, 1, 0, ep)
if !pass || err != nil {
- t.Log(hex.EncodeToString(ops.Program))
- t.Log(sb.String())
+ t.Log(ep.Trace.String())
}
require.NoError(t, err)
require.True(t, pass)
// should fail when accessing future transaction in group
- futureCreatableIDProg := `
-gaid 2
-int 0
->
-`
-
- ops = testProg(t, futureCreatableIDProg, 4)
- _, err = EvalStateful(ops.Program, ep)
+ check2 := testProg(t, "gaid 2; int 0; >", 4)
+ _, err = EvalApp(check2.Program, 1, 0, ep)
require.Error(t, err)
require.Contains(t, err.Error(), "gaid can't get creatable ID of txn ahead of the current one")
// should fail when accessing self
- ep.GroupIndex = 0
- ops = testProg(t, checkCreatableIDProg, 4)
- _, err = EvalStateful(ops.Program, ep)
+ _, err = EvalApp(check0.Program, 0, 0, ep)
require.Error(t, err)
require.Contains(t, err.Error(), "gaid is only for accessing creatable IDs of previous txns")
- ep.GroupIndex = 1
// should fail on non-creatable
ep.TxnGroup[0].Txn.Type = protocol.PaymentTx
- _, err = EvalStateful(ops.Program, ep)
+ _, err = EvalApp(check0.Program, 1, 0, ep)
require.Error(t, err)
require.Contains(t, err.Error(), "can't use gaid on txn that is not an app call nor an asset config txn")
ep.TxnGroup[0].Txn.Type = protocol.AssetConfigTx
-
- // should fail when no creatable was created
- ledger.SetTrackedCreatable(0, basics.CreatableLocator{})
- _, err = EvalStateful(ops.Program, ep)
- require.Error(t, err)
- require.Contains(t, err.Error(), "the txn did not create anything")
}
func TestGtxn(t *testing.T) {
@@ -1873,8 +1782,8 @@ gtxn 0 Sender
txn.Txn.SelectionPK[:],
txn.Txn.Note,
}
- ep := defaultEvalParams(nil, &txn)
- ep.TxnGroup = makeSampleTxnGroup(txn)
+ ep := defaultEvalParams(&txn)
+ ep.TxnGroup = transactions.WrapSignedTxnsWithAD(makeSampleTxnGroup(txn))
testLogic(t, source, v, ep)
if v >= 3 {
gtxnsProg := strings.ReplaceAll(source, "gtxn 0", "int 0; gtxns")
@@ -1889,27 +1798,64 @@ gtxn 0 Sender
}
}
-func testLogic(t *testing.T, program string, v uint64, ep EvalParams, problems ...string) {
+func testLogic(t *testing.T, program string, v uint64, ep *EvalParams, problems ...string) {
+ t.Helper()
ops := testProg(t, program, v)
+ testLogicBytes(t, ops.Program, ep, problems...)
+}
+
+func testLogicBytes(t *testing.T, program []byte, ep *EvalParams, problems ...string) {
+ t.Helper()
+ testLogicFull(t, program, 0, ep, problems...)
+}
+
+func testLogicFull(t *testing.T, program []byte, gi int, ep *EvalParams, problems ...string) {
+ t.Helper()
+
+ var checkProblem string
+ var evalProblem string
+ switch len(problems) {
+ case 2:
+ checkProblem = problems[0]
+ evalProblem = problems[1]
+ case 1:
+ evalProblem = problems[0]
+ case 0:
+ default:
+ require.Fail(t, "Misused testLogic: %d problems", len(problems))
+ }
+
sb := &strings.Builder{}
ep.Trace = sb
- ep.Txn.Lsig.Logic = ops.Program
- err := Check(ops.Program, ep)
- if err != nil {
- t.Log(hex.EncodeToString(ops.Program))
- t.Log(sb.String())
- }
- require.NoError(t, err)
- pass, err := Eval(ops.Program, ep)
- if len(problems) == 0 {
+ ep.TxnGroup[0].Lsig.Logic = program
+ err := CheckSignature(gi, ep)
+ if checkProblem == "" {
require.NoError(t, err, sb.String())
- require.True(t, pass, sb.String())
} else {
- require.Error(t, err, sb.String())
- for _, problem := range problems {
- require.Contains(t, err.Error(), problem)
- }
+ require.Error(t, err, "Check\n%s\nExpected: %v", sb, checkProblem)
+ require.Contains(t, err.Error(), checkProblem)
+ }
+
+ // We continue on to check Eval() of things that failed Check() because it's
+ // a nice confirmation that Check() is usually stricter than Eval(). This
+ // may mean that the problems argument is often duplicated, but this seems
+ // the best way to be concise about all sorts of tests.
+
+ pass, err := EvalSignature(gi, ep)
+ if evalProblem == "" {
+ require.NoError(t, err, "Eval%s\nExpected: PASS", sb)
+ assert.True(t, pass, "Eval%s\nExpected: PASS", sb)
+ return
+ }
+
+ // There is an evalProblem to check. REJECT is special and only means that
+ // the app didn't accept. Maybe it's an error, maybe it's just !pass.
+ if evalProblem == "REJECT" {
+ require.True(t, err != nil || !pass, "Eval%s\nExpected: REJECT", sb)
+ } else {
+ require.Error(t, err, "Eval%s\nExpected: %v", sb, evalProblem)
+ require.Contains(t, err.Error(), evalProblem)
}
}
@@ -1925,43 +1871,30 @@ txna ApplicationArgs 0
var txn transactions.SignedTxn
txn.Txn.Accounts = make([]basics.Address, 1)
txn.Txn.Accounts[0] = txn.Txn.Sender
- txn.Txn.ApplicationArgs = make([][]byte, 1)
- txn.Txn.ApplicationArgs[0] = []byte(protocol.PaymentTx)
- txgroup := make([]transactions.SignedTxn, 1)
- txgroup[0] = txn
- ep := defaultEvalParams(nil, &txn)
- ep.TxnGroup = txgroup
- _, err := Eval(ops.Program, ep)
- require.NoError(t, err)
+ txn.Txn.ApplicationArgs = [][]byte{txn.Txn.Sender[:]}
+ ep := defaultEvalParams(&txn)
+ testLogicBytes(t, ops.Program, ep)
// modify txn field
saved := ops.Program[2]
ops.Program[2] = 0x01
- _, err = Eval(ops.Program, ep)
- require.Error(t, err)
- require.Contains(t, err.Error(), "txna unsupported field")
+ testLogicBytes(t, ops.Program, ep, "unsupported array field")
// modify txn field to unknown one
ops.Program[2] = 99
- _, err = Eval(ops.Program, ep)
- require.Error(t, err)
- require.Contains(t, err.Error(), "invalid txn field 99")
+ testLogicBytes(t, ops.Program, ep, "invalid txn field 99")
// modify txn array index
ops.Program[2] = saved
saved = ops.Program[3]
ops.Program[3] = 0x02
- _, err = Eval(ops.Program, ep)
- require.Error(t, err)
- require.Contains(t, err.Error(), "invalid Accounts index")
+ testLogicBytes(t, ops.Program, ep, "invalid Accounts index")
// modify txn array index in the second opcode
ops.Program[3] = saved
saved = ops.Program[6]
ops.Program[6] = 0x01
- _, err = Eval(ops.Program, ep)
- require.Error(t, err)
- require.Contains(t, err.Error(), "invalid ApplicationArgs index")
+ testLogicBytes(t, ops.Program, ep, "invalid ApplicationArgs index")
ops.Program[6] = saved
// check special case: Account 0 == Sender
@@ -1973,48 +1906,36 @@ txn Sender
ops2 := testProg(t, source, AssemblerMaxVersion)
var txn2 transactions.SignedTxn
copy(txn2.Txn.Sender[:], []byte("aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00"))
- ep2 := defaultEvalParams(nil, &txn2)
- pass, err := Eval(ops2.Program, ep2)
- require.NoError(t, err)
- require.True(t, pass)
+ ep2 := defaultEvalParams(&txn2)
+ testLogicBytes(t, ops2.Program, ep2)
// check gtxna
source = `gtxna 0 Accounts 1
txna ApplicationArgs 0
==`
ops = testProg(t, source, AssemblerMaxVersion)
- require.NoError(t, err)
- _, err = Eval(ops.Program, ep)
- require.NoError(t, err)
+ testLogicBytes(t, ops.Program, ep)
// modify gtxn index
saved = ops.Program[2]
ops.Program[2] = 0x01
- _, err = Eval(ops.Program, ep)
- require.Error(t, err)
- require.Contains(t, err.Error(), "gtxna lookup TxnGroup[1] but it only has 1")
+ testLogicBytes(t, ops.Program, ep, "gtxna lookup TxnGroup[1] but it only has 1")
// modify gtxn field
ops.Program[2] = saved
saved = ops.Program[3]
ops.Program[3] = 0x01
- _, err = Eval(ops.Program, ep)
- require.Error(t, err)
- require.Contains(t, err.Error(), "gtxna unsupported field")
+ testLogicBytes(t, ops.Program, ep, "unsupported array field")
// modify gtxn field to unknown one
ops.Program[3] = 99
- _, err = Eval(ops.Program, ep)
- require.Error(t, err)
- require.Contains(t, err.Error(), "invalid txn field 99")
+ testLogicBytes(t, ops.Program, ep, "invalid txn field 99")
// modify gtxn array index
ops.Program[3] = saved
saved = ops.Program[4]
ops.Program[4] = 0x02
- _, err = Eval(ops.Program, ep)
- require.Error(t, err)
- require.Contains(t, err.Error(), "invalid Accounts index")
+ testLogicBytes(t, ops.Program, ep, "invalid Accounts index")
ops.Program[4] = saved
// check special case: Account 0 == Sender
@@ -2026,13 +1947,8 @@ txn Sender
ops3 := testProg(t, source, AssemblerMaxVersion)
var txn3 transactions.SignedTxn
copy(txn2.Txn.Sender[:], []byte("aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00"))
- txgroup3 := make([]transactions.SignedTxn, 1)
- txgroup3[0] = txn3
- ep3 := defaultEvalParams(nil, &txn3)
- ep3.TxnGroup = txgroup3
- pass, err = Eval(ops3.Program, ep3)
- require.NoError(t, err)
- require.True(t, pass)
+ ep3 := defaultEvalParams(&txn3)
+ testLogicBytes(t, ops3.Program, ep3)
}
// check empty values in ApplicationArgs and Account
@@ -2050,42 +1966,24 @@ int 0
var txn transactions.SignedTxn
txn.Txn.ApplicationArgs = make([][]byte, 1)
txn.Txn.ApplicationArgs[0] = []byte("")
- txgroup := make([]transactions.SignedTxn, 1)
- txgroup[0] = txn
- ep := defaultEvalParams(nil, &txn)
- ep.TxnGroup = txgroup
- pass, err := Eval(ops.Program, ep)
- require.NoError(t, err)
- require.True(t, pass)
+ testLogicBytes(t, ops.Program, defaultEvalParams(&txn))
+
txn.Txn.ApplicationArgs[0] = nil
- txgroup[0] = txn
- ep.TxnGroup = txgroup
- pass, err = Eval(ops.Program, ep)
- require.NoError(t, err)
- require.True(t, pass)
+ testLogicBytes(t, ops.Program, defaultEvalParams(&txn))
source2 := `txna Accounts 1
global ZeroAddress
==
`
- ops2 := testProg(t, source2, AssemblerMaxVersion)
+ ops = testProg(t, source2, AssemblerMaxVersion)
var txn2 transactions.SignedTxn
txn2.Txn.Accounts = make([]basics.Address, 1)
txn2.Txn.Accounts[0] = basics.Address{}
- txgroup2 := make([]transactions.SignedTxn, 1)
- txgroup2[0] = txn2
- ep2 := defaultEvalParams(nil, &txn2)
- ep2.TxnGroup = txgroup2
- pass, err = Eval(ops2.Program, ep2)
- require.NoError(t, err)
- require.True(t, pass)
+ testLogicBytes(t, ops.Program, defaultEvalParams(&txn2))
+
txn2.Txn.Accounts = make([]basics.Address, 1)
- txgroup2[0] = txn
- ep2.TxnGroup = txgroup2
- pass, err = Eval(ops2.Program, ep2)
- require.NoError(t, err)
- require.True(t, pass)
+ testLogicBytes(t, ops.Program, defaultEvalParams(&txn2))
}
func TestTxnas(t *testing.T) {
@@ -2103,14 +2001,9 @@ txnas ApplicationArgs
var txn transactions.SignedTxn
txn.Txn.Accounts = make([]basics.Address, 1)
txn.Txn.Accounts[0] = txn.Txn.Sender
- txn.Txn.ApplicationArgs = make([][]byte, 1)
- txn.Txn.ApplicationArgs[0] = []byte(protocol.PaymentTx)
- txgroup := make([]transactions.SignedTxn, 1)
- txgroup[0] = txn
- ep := defaultEvalParams(nil, &txn)
- ep.TxnGroup = txgroup
- _, err := Eval(ops.Program, ep)
- require.NoError(t, err)
+ txn.Txn.ApplicationArgs = [][]byte{txn.Txn.Sender[:]}
+ ep := defaultEvalParams(&txn)
+ testLogicBytes(t, ops.Program, ep)
// check special case: Account 0 == Sender
// even without any additional context
@@ -2119,13 +2012,10 @@ txnas Accounts
txn Sender
==
`
- ops2 := testProg(t, source, AssemblerMaxVersion)
+ ops = testProg(t, source, AssemblerMaxVersion)
var txn2 transactions.SignedTxn
copy(txn2.Txn.Sender[:], []byte("aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00"))
- ep2 := defaultEvalParams(nil, &txn2)
- pass, err := Eval(ops2.Program, ep2)
- require.NoError(t, err)
- require.True(t, pass)
+ testLogicBytes(t, ops.Program, defaultEvalParams(&txn2))
// check gtxnas
source = `int 1
@@ -2133,9 +2023,7 @@ gtxnas 0 Accounts
txna ApplicationArgs 0
==`
ops = testProg(t, source, AssemblerMaxVersion)
- require.NoError(t, err)
- _, err = Eval(ops.Program, ep)
- require.NoError(t, err)
+ testLogicBytes(t, ops.Program, ep)
// check special case: Account 0 == Sender
// even without any additional context
@@ -2144,16 +2032,10 @@ gtxnas 0 Accounts
txn Sender
==
`
- ops3 := testProg(t, source, AssemblerMaxVersion)
+ ops = testProg(t, source, AssemblerMaxVersion)
var txn3 transactions.SignedTxn
- copy(txn2.Txn.Sender[:], []byte("aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00"))
- txgroup3 := make([]transactions.SignedTxn, 1)
- txgroup3[0] = txn3
- ep3 := defaultEvalParams(nil, &txn3)
- ep3.TxnGroup = txgroup3
- pass, err = Eval(ops3.Program, ep3)
- require.NoError(t, err)
- require.True(t, pass)
+ copy(txn3.Txn.Sender[:], []byte("aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00"))
+ testLogicBytes(t, ops.Program, defaultEvalParams(&txn3))
// check gtxnsas
source = `int 0
@@ -2162,9 +2044,7 @@ gtxnsas Accounts
txna ApplicationArgs 0
==`
ops = testProg(t, source, AssemblerMaxVersion)
- require.NoError(t, err)
- _, err = Eval(ops.Program, ep)
- require.NoError(t, err)
+ testLogicBytes(t, ops.Program, ep)
}
func TestBitOps(t *testing.T) {
@@ -2244,17 +2124,17 @@ func TestSubstringFlop(t *testing.T) {
// fails in compiler
testProg(t, `byte 0xf000000000000000
substring
-len`, 2, expect{2, "substring expects 2 immediate arguments"})
+len`, 2, Expect{2, "substring expects 2 immediate arguments"})
// fails in compiler
testProg(t, `byte 0xf000000000000000
substring 1
-len`, 2, expect{2, "substring expects 2 immediate arguments"})
+len`, 2, Expect{2, "substring expects 2 immediate arguments"})
// fails in compiler
testProg(t, `byte 0xf000000000000000
substring 4 2
-len`, 2, expect{2, "substring end is before start"})
+len`, 2, Expect{2, "substring end is before start"})
// fails at runtime
testPanics(t, `byte 0xf000000000000000
@@ -2307,11 +2187,11 @@ func TestExtractFlop(t *testing.T) {
// fails in compiler
testProg(t, `byte 0xf000000000000000
extract
- len`, 5, expect{2, "extract expects 2 immediate arguments"})
+ len`, 5, Expect{2, "extract expects 2 immediate arguments"})
testProg(t, `byte 0xf000000000000000
extract 1
- len`, 5, expect{2, "extract expects 2 immediate arguments"})
+ len`, 5, Expect{2, "extract expects 2 immediate arguments"})
// fails at runtime
err := testPanics(t, `byte 0xf000000000000000
@@ -2432,6 +2312,7 @@ func TestGload(t *testing.T) {
// for simple app-call-only transaction groups
type scratchTestCase struct {
tealSources []string
+ errTxn int
errContains string
}
@@ -2480,6 +2361,7 @@ store 0
int 1
`,
},
+ errTxn: 0,
errContains: "can't use gload on self, use load instead",
}
@@ -2494,67 +2376,29 @@ int 2
store 0
int 1`,
},
+ errTxn: 0,
errContains: "gload can't get future scratch space from txn with index 1",
}
cases := []scratchTestCase{
simpleCase, multipleTxnCase, selfCase, laterTxnSlotCase,
}
- proto := defaultEvalProtoWithVersion(LogicVersion)
for i, testCase := range cases {
t.Run(fmt.Sprintf("i=%d", i), func(t *testing.T) {
sources := testCase.tealSources
- // Assemble ops
- opsList := make([]*OpStream, len(sources))
- for j, source := range sources {
- ops := testProg(t, source, AssemblerMaxVersion)
- opsList[j] = ops
- }
- // Initialize txgroup and cxgroup
+ // Initialize txgroup
txgroup := make([]transactions.SignedTxn, len(sources))
for j := range txgroup {
- txgroup[j] = transactions.SignedTxn{
- Txn: transactions.Transaction{
- Type: protocol.ApplicationCallTx,
- },
- }
+ txgroup[j].Txn.Type = protocol.ApplicationCallTx
}
- // Construct EvalParams
- pastSideEffects := MakePastSideEffects(len(sources))
- epList := make([]EvalParams, len(sources))
- for j := range sources {
- epList[j] = EvalParams{
- Proto: &proto,
- Txn: &txgroup[j],
- TxnGroup: txgroup,
- GroupIndex: uint64(j),
- PastSideEffects: pastSideEffects,
- }
- }
-
- // Evaluate app calls
- shouldErr := testCase.errContains != ""
- didPass := true
- for j, ops := range opsList {
- pass, err := EvalStateful(ops.Program, epList[j])
-
- // Confirm it errors or that the error message is the expected one
- if !shouldErr {
- require.NoError(t, err)
- } else if shouldErr && err != nil {
- require.Error(t, err)
- require.Contains(t, err.Error(), testCase.errContains)
- }
-
- if !pass {
- didPass = false
- }
+ if testCase.errContains != "" {
+ testApps(t, sources, txgroup, LogicVersion, MakeLedger(nil), Expect{testCase.errTxn, testCase.errContains})
+ } else {
+ testApps(t, sources, txgroup, LogicVersion, MakeLedger(nil))
}
-
- require.Equal(t, !shouldErr, didPass)
})
}
@@ -2588,42 +2432,30 @@ int 1`,
failCases := []failureCase{nonAppCall, logicSigCall}
for j, failCase := range failCases {
t.Run(fmt.Sprintf("j=%d", j), func(t *testing.T) {
- source := "gload 0 0"
- ops := testProg(t, source, AssemblerMaxVersion)
+ program := testProg(t, "gload 0 0", AssemblerMaxVersion).Program
- // Initialize txgroup and cxgroup
- txgroup := make([]transactions.SignedTxn, 2)
- txgroup[0] = failCase.firstTxn
- txgroup[1] = transactions.SignedTxn{}
-
- // Construct EvalParams
- pastSideEffects := MakePastSideEffects(2)
- epList := make([]EvalParams, 2)
- for j := range epList {
- epList[j] = EvalParams{
- Proto: &proto,
- Txn: &txgroup[j],
- TxnGroup: txgroup,
- GroupIndex: uint64(j),
- PastSideEffects: pastSideEffects,
- }
+ txgroup := []transactions.SignedTxnWithAD{
+ {SignedTxn: failCase.firstTxn},
+ {},
+ }
+
+ ep := &EvalParams{
+ Proto: makeTestProto(),
+ TxnGroup: txgroup,
+ pastScratch: make([]*scratchSpace, 2),
}
- // Evaluate app call
- var err error
switch failCase.runMode {
case runModeApplication:
- _, err = EvalStateful(ops.Program, epList[1])
+ testAppBytes(t, program, ep, failCase.errContains)
default:
- _, err = Eval(ops.Program, epList[1])
+ testLogicBytes(t, program, ep, failCase.errContains, failCase.errContains)
}
-
- require.Error(t, err)
- require.Contains(t, err.Error(), failCase.errContains)
})
}
}
+// TestGloads tests gloads and gloadss
func TestGloads(t *testing.T) {
partitiontest.PartitionTest(t)
@@ -2643,51 +2475,35 @@ int 0
gloads 0
byte "txn 1"
==
+assert
int 1
gloads 1
byte "txn 2"
==
-&&`
+assert
+int 0
+int 0
+gloadss
+byte "txn 1"
+==
+assert
+int 1
+int 1
+gloadss
+byte "txn 2"
+==
+assert
+int 1
+`
sources := []string{source1, source2, source3}
- proto := defaultEvalProtoWithVersion(LogicVersion)
- // Assemble ops
- opsList := make([]*OpStream, len(sources))
- for j, source := range sources {
- ops := testProg(t, source, AssemblerMaxVersion)
- opsList[j] = ops
- }
-
- // Initialize txgroup and cxgroup
txgroup := make([]transactions.SignedTxn, len(sources))
for j := range txgroup {
- txgroup[j] = transactions.SignedTxn{
- Txn: transactions.Transaction{
- Type: protocol.ApplicationCallTx,
- },
- }
+ txgroup[j].Txn.Type = protocol.ApplicationCallTx
}
- // Construct EvalParams
- pastSideEffects := MakePastSideEffects(len(sources))
- epList := make([]EvalParams, len(sources))
- for j := range sources {
- epList[j] = EvalParams{
- Proto: &proto,
- Txn: &txgroup[j],
- TxnGroup: txgroup,
- GroupIndex: uint64(j),
- PastSideEffects: pastSideEffects,
- }
- }
-
- // Evaluate app calls
- for j, ops := range opsList {
- pass, err := EvalStateful(ops.Program, epList[j])
- require.NoError(t, err)
- require.True(t, pass)
- }
+ testApps(t, sources, txgroup, LogicVersion, MakeLedger(nil))
}
const testCompareProgramText = `int 35
@@ -2775,35 +2591,25 @@ func TestSlowLogic(t *testing.T) {
// v1overspend fails (on v1)
ops := testProg(t, v1overspend, 1)
- err := Check(ops.Program, defaultEvalParamsWithVersion(nil, nil, 1))
- require.Error(t, err)
- require.Contains(t, err.Error(), "static cost")
- // v2overspend passes Check, even on v2 proto, because cost is "grandfathered"
+ // We should never Eval this after it fails Check(), but nice to see it also fails.
+ testLogicBytes(t, ops.Program, defaultEvalParamsWithVersion(nil, 1),
+ "static cost", "dynamic cost")
+ // v2overspend passes Check, even on v2 proto, because the old low cost is "grandfathered"
ops = testProg(t, v2overspend, 1)
- err = Check(ops.Program, defaultEvalParamsWithVersion(nil, nil, 2))
- require.NoError(t, err)
+ testLogicBytes(t, ops.Program, defaultEvalParamsWithVersion(nil, 2))
// even the shorter, v2overspend, fails when compiled as v2 code
ops = testProg(t, v2overspend, 2)
- err = Check(ops.Program, defaultEvalParamsWithVersion(nil, nil, 2))
- require.Error(t, err)
- require.Contains(t, err.Error(), "static cost")
+ testLogicBytes(t, ops.Program, defaultEvalParamsWithVersion(nil, 2),
+ "static cost", "dynamic cost")
// in v4 cost is still 134, but only matters in Eval, not Check, so both fail there
- ep4 := defaultEvalParamsWithVersion(nil, nil, 4)
+ ep4 := defaultEvalParamsWithVersion(nil, 4)
ops = testProg(t, v1overspend, 4)
- err = Check(ops.Program, ep4)
- require.NoError(t, err)
- _, err = Eval(ops.Program, ep4)
- require.Error(t, err)
- require.Contains(t, err.Error(), "dynamic cost")
+ testLogicBytes(t, ops.Program, ep4, "dynamic cost")
ops = testProg(t, v2overspend, 4)
- err = Check(ops.Program, ep4)
- require.NoError(t, err)
- _, err = Eval(ops.Program, ep4)
- require.Error(t, err)
- require.Contains(t, err.Error(), "dynamic cost")
+ testLogicBytes(t, ops.Program, ep4, "dynamic cost")
}
func isNotPanic(t *testing.T, err error) {
@@ -2823,16 +2629,7 @@ func TestStackUnderflow(t *testing.T) {
t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) {
ops := testProg(t, `int 1`, v)
ops.Program = append(ops.Program, 0x08) // +
- err := Check(ops.Program, defaultEvalParams(nil, nil))
- require.NoError(t, err)
- sb := strings.Builder{}
- pass, err := Eval(ops.Program, defaultEvalParams(&sb, nil))
- if pass {
- t.Log(hex.EncodeToString(ops.Program))
- t.Log(sb.String())
- }
- require.False(t, pass)
- isNotPanic(t, err)
+ testLogicBytes(t, ops.Program, defaultEvalParams(nil), "stack underflow")
})
}
}
@@ -2845,16 +2642,7 @@ func TestWrongStackTypeRuntime(t *testing.T) {
t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) {
ops := testProg(t, `int 1`, v)
ops.Program = append(ops.Program, 0x01, 0x15) // sha256, len
- err := Check(ops.Program, defaultEvalParams(nil, nil))
- require.NoError(t, err)
- sb := strings.Builder{}
- pass, err := Eval(ops.Program, defaultEvalParams(&sb, nil))
- if pass {
- t.Log(hex.EncodeToString(ops.Program))
- t.Log(sb.String())
- }
- require.False(t, pass)
- isNotPanic(t, err)
+ testLogicBytes(t, ops.Program, defaultEvalParams(nil), "sha256 arg 0 wanted")
})
}
}
@@ -2867,16 +2655,8 @@ func TestEqMismatch(t *testing.T) {
t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) {
ops := testProg(t, `byte 0x1234; int 1`, v)
ops.Program = append(ops.Program, 0x12) // ==
- err := Check(ops.Program, defaultEvalParams(nil, nil))
- require.NoError(t, err) // TODO: Check should know the type stack was wrong
- sb := strings.Builder{}
- pass, err := Eval(ops.Program, defaultEvalParams(&sb, nil))
- if pass {
- t.Log(hex.EncodeToString(ops.Program))
- t.Log(sb.String())
- }
- require.False(t, pass)
- isNotPanic(t, err)
+ testLogicBytes(t, ops.Program, defaultEvalParams(nil), "cannot compare")
+ // TODO: Check should know the type stack was wrong
})
}
}
@@ -2889,16 +2669,7 @@ func TestNeqMismatch(t *testing.T) {
t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) {
ops := testProg(t, `byte 0x1234; int 1`, v)
ops.Program = append(ops.Program, 0x13) // !=
- err := Check(ops.Program, defaultEvalParams(nil, nil))
- require.NoError(t, err) // TODO: Check should know the type stack was wrong
- sb := strings.Builder{}
- pass, err := Eval(ops.Program, defaultEvalParams(&sb, nil))
- if pass {
- t.Log(hex.EncodeToString(ops.Program))
- t.Log(sb.String())
- }
- require.False(t, pass)
- isNotPanic(t, err)
+ testLogicBytes(t, ops.Program, defaultEvalParams(nil), "cannot compare")
})
}
}
@@ -2911,16 +2682,7 @@ func TestWrongStackTypeRuntime2(t *testing.T) {
t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) {
ops := testProg(t, `byte 0x1234; int 1`, v)
ops.Program = append(ops.Program, 0x08) // +
- err := Check(ops.Program, defaultEvalParams(nil, nil))
- require.NoError(t, err)
- sb := strings.Builder{}
- pass, _ := Eval(ops.Program, defaultEvalParams(&sb, nil))
- if pass {
- t.Log(hex.EncodeToString(ops.Program))
- t.Log(sb.String())
- }
- require.False(t, pass)
- isNotPanic(t, err)
+ testLogicBytes(t, ops.Program, defaultEvalParams(nil), "+ arg 0 wanted")
})
}
}
@@ -2938,16 +2700,7 @@ func TestIllegalOp(t *testing.T) {
break
}
}
- err := Check(ops.Program, defaultEvalParams(nil, nil))
- require.Error(t, err)
- sb := strings.Builder{}
- pass, err := Eval(ops.Program, defaultEvalParams(&sb, nil))
- if pass {
- t.Log(hex.EncodeToString(ops.Program))
- t.Log(sb.String())
- }
- require.False(t, pass)
- isNotPanic(t, err)
+ testLogicBytes(t, ops.Program, defaultEvalParams(nil), "illegal opcode", "illegal opcode")
})
}
}
@@ -2965,16 +2718,8 @@ int 1
`, v)
// cut two last bytes - intc_1 and last byte of bnz
ops.Program = ops.Program[:len(ops.Program)-2]
- err := Check(ops.Program, defaultEvalParams(nil, nil))
- require.Error(t, err)
- sb := strings.Builder{}
- pass, err := Eval(ops.Program, defaultEvalParams(&sb, nil))
- if pass {
- t.Log(hex.EncodeToString(ops.Program))
- t.Log(sb.String())
- }
- require.False(t, pass)
- isNotPanic(t, err)
+ testLogicBytes(t, ops.Program, defaultEvalParams(nil),
+ "bnz program ends short", "bnz program ends short")
})
}
}
@@ -2988,13 +2733,9 @@ intc 0
intc 0
bnz done
done:`, 2)
- err := Check(ops.Program, defaultEvalParams(nil, nil))
- require.NoError(t, err)
- sb := strings.Builder{}
- pass, err := Eval(ops.Program, defaultEvalParams(&sb, nil))
- require.NoError(t, err)
- require.True(t, pass)
+ testLogicBytes(t, ops.Program, defaultEvalParams(nil))
}
+
func TestShortBytecblock(t *testing.T) {
partitiontest.PartitionTest(t)
@@ -3007,17 +2748,8 @@ func TestShortBytecblock(t *testing.T) {
for i := 2; i < len(fullops.Program); i++ {
program := fullops.Program[:i]
t.Run(hex.EncodeToString(program), func(t *testing.T) {
- err := Check(program, defaultEvalParams(nil, nil))
- require.Error(t, err)
- isNotPanic(t, err)
- sb := strings.Builder{}
- pass, err := Eval(program, defaultEvalParams(&sb, nil))
- if pass {
- t.Log(hex.EncodeToString(program))
- t.Log(sb.String())
- }
- require.False(t, pass)
- isNotPanic(t, err)
+ testLogicBytes(t, program, defaultEvalParams(nil),
+ "bytecblock", "bytecblock")
})
}
})
@@ -3038,17 +2770,7 @@ func TestShortBytecblock2(t *testing.T) {
t.Run(src, func(t *testing.T) {
program, err := hex.DecodeString(src)
require.NoError(t, err)
- err = Check(program, defaultEvalParams(nil, nil))
- require.Error(t, err)
- isNotPanic(t, err)
- sb := strings.Builder{}
- pass, err := Eval(program, defaultEvalParams(&sb, nil))
- if pass {
- t.Log(hex.EncodeToString(program))
- t.Log(sb.String())
- }
- require.False(t, pass)
- isNotPanic(t, err)
+ testLogicBytes(t, program, defaultEvalParams(nil), "bytecblock", "bytecblock")
})
}
}
@@ -3068,8 +2790,7 @@ func TestPanic(t *testing.T) {
log := logging.TestingLog(t)
for v := uint64(1); v <= AssemblerMaxVersion; v++ {
t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) {
- ops, err := AssembleStringWithVersion(`int 1`, v)
- require.NoError(t, err)
+ ops := testProg(t, `int 1`, v)
var hackedOpcode int
var oldSpec OpSpec
for opcode, spec := range opsByOpcode[v] {
@@ -3083,10 +2804,10 @@ func TestPanic(t *testing.T) {
break
}
}
- sb := strings.Builder{}
- params := defaultEvalParams(&sb, nil)
- params.Logger = log
- err = Check(ops.Program, params)
+ params := defaultEvalParams(nil)
+ params.logger = log
+ params.TxnGroup[0].Lsig.Logic = ops.Program
+ err := CheckSignature(0, params)
require.Error(t, err)
if pe, ok := err.(PanicError); ok {
require.Equal(t, panicString, pe.PanicValue)
@@ -3095,15 +2816,14 @@ func TestPanic(t *testing.T) {
} else {
t.Errorf("expected PanicError object but got %T %#v", err, err)
}
- sb = strings.Builder{}
var txn transactions.SignedTxn
txn.Lsig.Logic = ops.Program
- params = defaultEvalParams(&sb, &txn)
- params.Logger = log
- pass, err := Eval(ops.Program, params)
+ params = defaultEvalParams(&txn)
+ params.logger = log
+ pass, err := EvalSignature(0, params)
if pass {
t.Log(hex.EncodeToString(ops.Program))
- t.Log(sb.String())
+ t.Log(params.Trace.String())
}
require.False(t, pass)
if pe, ok := err.(PanicError); ok {
@@ -3124,13 +2844,8 @@ func TestProgramTooNew(t *testing.T) {
t.Parallel()
var program [12]byte
vlen := binary.PutUvarint(program[:], EvalMaxVersion+1)
- err := Check(program[:vlen], defaultEvalParams(nil, nil))
- require.Error(t, err)
- isNotPanic(t, err)
- pass, err := Eval(program[:vlen], defaultEvalParams(nil, nil))
- require.Error(t, err)
- require.False(t, pass)
- isNotPanic(t, err)
+ testLogicBytes(t, program[:vlen], defaultEvalParams(nil),
+ "greater than max supported", "greater than max supported")
}
func TestInvalidVersion(t *testing.T) {
@@ -3139,13 +2854,7 @@ func TestInvalidVersion(t *testing.T) {
t.Parallel()
program, err := hex.DecodeString("ffffffffffffffffffffffff")
require.NoError(t, err)
- err = Check(program, defaultEvalParams(nil, nil))
- require.Error(t, err)
- isNotPanic(t, err)
- pass, err := Eval(program, defaultEvalParams(nil, nil))
- require.Error(t, err)
- require.False(t, pass)
- isNotPanic(t, err)
+ testLogicBytes(t, program, defaultEvalParams(nil), "invalid version", "invalid version")
}
func TestProgramProtoForbidden(t *testing.T) {
@@ -3154,18 +2863,11 @@ func TestProgramProtoForbidden(t *testing.T) {
t.Parallel()
var program [12]byte
vlen := binary.PutUvarint(program[:], EvalMaxVersion)
- proto := config.ConsensusParams{
+ ep := defaultEvalParams(nil)
+ ep.Proto = &config.ConsensusParams{
LogicSigVersion: EvalMaxVersion - 1,
}
- ep := EvalParams{}
- ep.Proto = &proto
- err := Check(program[:vlen], ep)
- require.Error(t, err)
- ep.Txn = &transactions.SignedTxn{}
- pass, err := Eval(program[:vlen], ep)
- require.Error(t, err)
- require.False(t, pass)
- isNotPanic(t, err)
+ testLogicBytes(t, program[:vlen], ep, "greater than protocol", "greater than protocol")
}
func TestMisalignedBranch(t *testing.T) {
@@ -3174,41 +2876,29 @@ func TestMisalignedBranch(t *testing.T) {
t.Parallel()
for v := uint64(1); v <= AssemblerMaxVersion; v++ {
t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) {
- ops, err := AssembleStringWithVersion(`int 1
+ ops := testProg(t, `int 1
bnz done
bytecblock 0x01234576 0xababcdcd 0xf000baad
done:
int 1`, v)
- require.NoError(t, err)
//t.Log(hex.EncodeToString(program))
canonicalProgramString := mutateProgVersion(v, "01200101224000112603040123457604ababcdcd04f000baad22")
canonicalProgramBytes, err := hex.DecodeString(canonicalProgramString)
require.NoError(t, err)
require.Equal(t, ops.Program, canonicalProgramBytes)
ops.Program[7] = 3 // clobber the branch offset to be in the middle of the bytecblock
- err = Check(ops.Program, defaultEvalParams(nil, nil))
- require.Error(t, err)
- require.Contains(t, err.Error(), "aligned")
- pass, err := Eval(ops.Program, defaultEvalParams(nil, nil))
- require.Error(t, err)
- require.False(t, pass)
- isNotPanic(t, err)
+ // Since Eval() doesn't know the jump is bad, we reject "by luck"
+ testLogicBytes(t, ops.Program, defaultEvalParams(nil), "aligned", "REJECT")
// back branches are checked differently, so test misaligned back branch
ops.Program[6] = 0xff // Clobber the two bytes of offset with 0xff 0xff = -1
ops.Program[7] = 0xff // That jumps into the offset itself (pc + 3 -1)
- err = Check(ops.Program, defaultEvalParams(nil, nil))
- require.Error(t, err)
if v < backBranchEnabledVersion {
- require.Contains(t, err.Error(), "negative branch")
+ testLogicBytes(t, ops.Program, defaultEvalParams(nil), "negative branch", "negative branch")
} else {
- require.Contains(t, err.Error(), "back branch")
- require.Contains(t, err.Error(), "aligned")
+ // Again, if we were ever to Eval(), we would not know it's wrong. But we reject here "by luck"
+ testLogicBytes(t, ops.Program, defaultEvalParams(nil), "back branch target", "REJECT")
}
- pass, err = Eval(ops.Program, defaultEvalParams(nil, nil))
- require.Error(t, err)
- require.False(t, pass)
- isNotPanic(t, err)
})
}
}
@@ -3219,25 +2909,19 @@ func TestBranchTooFar(t *testing.T) {
t.Parallel()
for v := uint64(1); v <= AssemblerMaxVersion; v++ {
t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) {
- ops, err := AssembleStringWithVersion(`int 1
+ ops := testProg(t, `int 1
bnz done
bytecblock 0x01234576 0xababcdcd 0xf000baad
done:
int 1`, v)
- require.NoError(t, err)
//t.Log(hex.EncodeToString(ops.Program))
canonicalProgramString := mutateProgVersion(v, "01200101224000112603040123457604ababcdcd04f000baad22")
canonicalProgramBytes, err := hex.DecodeString(canonicalProgramString)
require.NoError(t, err)
require.Equal(t, ops.Program, canonicalProgramBytes)
ops.Program[7] = 200 // clobber the branch offset to be beyond the end of the program
- err = Check(ops.Program, defaultEvalParams(nil, nil))
- require.Error(t, err)
- require.True(t, strings.Contains(err.Error(), "beyond end of program"))
- pass, err := Eval(ops.Program, defaultEvalParams(nil, nil))
- require.Error(t, err)
- require.False(t, pass)
- isNotPanic(t, err)
+ testLogicBytes(t, ops.Program, defaultEvalParams(nil),
+ "beyond end of program", "beyond end of program")
})
}
}
@@ -3248,12 +2932,11 @@ func TestBranchTooLarge(t *testing.T) {
t.Parallel()
for v := uint64(1); v <= AssemblerMaxVersion; v++ {
t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) {
- ops, err := AssembleStringWithVersion(`int 1
+ ops := testProg(t, `int 1
bnz done
bytecblock 0x01234576 0xababcdcd 0xf000baad
done:
int 1`, v)
- require.NoError(t, err)
//t.Log(hex.EncodeToString(ops.Program))
// (br)anch byte, (hi)gh byte of offset, (lo)w byte: brhilo
canonicalProgramString := mutateProgVersion(v, "01200101224000112603040123457604ababcdcd04f000baad22")
@@ -3261,14 +2944,7 @@ int 1`, v)
require.NoError(t, err)
require.Equal(t, ops.Program, canonicalProgramBytes)
ops.Program[6] = 0x70 // clobber hi byte of branch offset
- err = Check(ops.Program, defaultEvalParams(nil, nil))
- require.Error(t, err)
- require.Contains(t, err.Error(), "beyond")
- pass, err := Eval(ops.Program, defaultEvalParams(nil, nil))
- require.Error(t, err)
- require.Contains(t, err.Error(), "beyond")
- require.False(t, pass)
- isNotPanic(t, err)
+ testLogicBytes(t, ops.Program, defaultEvalParams(nil), "beyond", "beyond")
})
}
branches := []string{
@@ -3281,7 +2957,6 @@ intc_0
done:
intc_1
`
- ep := defaultEvalParams(nil, nil)
for _, line := range branches {
t.Run(fmt.Sprintf("branch=%s", line), func(t *testing.T) {
source := fmt.Sprintf(template, line)
@@ -3289,13 +2964,8 @@ intc_1
require.NoError(t, err)
ops.Program[7] = 0xf0 // clobber the branch offset - highly negative
ops.Program[8] = 0xff // clobber the branch offset
- err = Check(ops.Program, ep)
- require.Error(t, err)
- require.Contains(t, err.Error(), "beyond")
- pass, err := Eval(ops.Program, ep)
- require.Error(t, err)
- require.Contains(t, err.Error(), "beyond")
- require.False(t, pass)
+ testLogicBytes(t, ops.Program, defaultEvalParams(nil),
+ "branch target beyond", "branch target beyond")
})
}
}
@@ -3579,12 +3249,15 @@ int 142791994204213819
func evalLoop(b *testing.B, runs int, program []byte) {
b.ResetTimer()
for i := 0; i < runs; i++ {
- pass, err := Eval(program, benchmarkEvalParams(nil, nil))
+ var txn transactions.SignedTxn
+ txn.Lsig.Logic = program
+ pass, err := EvalSignature(0, benchmarkEvalParams(&txn))
if !pass {
// rerun to trace it. tracing messes up timing too much
- sb := strings.Builder{}
- pass, err = Eval(program, benchmarkEvalParams(&sb, nil))
- b.Log(sb.String())
+ ep := benchmarkEvalParams(&txn)
+ ep.Trace = &strings.Builder{}
+ pass, err = EvalSignature(0, ep)
+ b.Log(ep.Trace.String())
}
// require is super slow but makes useful error messages, wrap it in a check that makes the benchmark run a bunch faster
if err != nil {
@@ -3598,8 +3271,6 @@ func evalLoop(b *testing.B, runs int, program []byte) {
func benchmarkBasicProgram(b *testing.B, source string) {
ops := testProg(b, source, AssemblerMaxVersion)
- err := Check(ops.Program, defaultEvalParams(nil, nil))
- require.NoError(b, err)
evalLoop(b, b.N, ops.Program)
}
@@ -3615,8 +3286,6 @@ func benchmarkOperation(b *testing.B, prefix string, operation string, suffix st
source := prefix + ";" + strings.Repeat(operation+";", 2000) + ";" + suffix
source = strings.ReplaceAll(source, ";", "\n")
ops := testProg(b, source, AssemblerMaxVersion)
- err := Check(ops.Program, defaultEvalParams(nil, nil))
- require.NoError(b, err)
evalLoop(b, runs, ops.Program)
b.ReportMetric(float64(inst)*15.0, "waste/op")
}
@@ -3690,6 +3359,7 @@ func BenchmarkBigMath(b *testing.B) {
{"b*", "", "byte 0x01234576; byte 0x0223627389; b*; pop", "int 1"},
{"b/", "", "byte 0x0123457673624736; byte 0x0223627389; b/; pop", "int 1"},
{"b%", "", "byte 0x0123457673624736; byte 0x0223627389; b/; pop", "int 1"},
+ {"bsqrt", "", "byte 0x0123457673624736; bsqrt; pop", "int 1"},
{"b+big", // u256 + u256
"byte 0x0123457601234576012345760123457601234576012345760123457601234576",
@@ -3711,6 +3381,10 @@ func BenchmarkBigMath(b *testing.B) {
`byte 0xa123457601234576012345760123457601234576012345760123457601234576
byte 0x34576012345760123457601234576312; b/; pop`,
"int 1"},
+ {"bsqrt-big", "",
+ `byte 0xa123457601234576012345760123457601234576012345760123457601234576
+ bsqrt; pop`,
+ "int 1"},
}
for _, bench := range benches {
b.Run(bench[0], func(b *testing.B) {
@@ -3792,16 +3466,17 @@ func BenchmarkCheckx5(b *testing.B) {
addBenchmark2Source,
}
- programs := make([]*OpStream, len(sourcePrograms))
- var err error
+ programs := make([][]byte, len(sourcePrograms))
for i, text := range sourcePrograms {
- programs[i], err = AssembleStringWithVersion(text, AssemblerMaxVersion)
- require.NoError(b, err)
+ ops := testProg(b, text, AssemblerMaxVersion)
+ programs[i] = ops.Program
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
for _, program := range programs {
- err = Check(program.Program, defaultEvalParams(nil, nil))
+ var txn transactions.SignedTxn
+ txn.Lsig.Logic = program
+ err := CheckSignature(0, defaultEvalParams(&txn))
if err != nil {
require.NoError(b, err)
}
@@ -3839,23 +3514,21 @@ pop
`
ops := testProg(t, text, AssemblerMaxVersion)
- ep := defaultEvalParams(nil, nil)
- ep.Txn = &transactions.SignedTxn{}
- ep.Txn.Txn.ApplicationArgs = [][]byte{[]byte("test")}
- _, err := Eval(ops.Program, ep)
- require.NoError(t, err)
+ var txn transactions.SignedTxn
+ txn.Lsig.Logic = ops.Program
+ txn.Txn.ApplicationArgs = [][]byte{[]byte("test")}
- ep = defaultEvalParamsV1(nil, nil)
- _, err = Eval(ops.Program, ep)
- require.Error(t, err)
- require.Contains(t, err.Error(), "greater than protocol supported version 1")
+ ep := defaultEvalParams(&txn)
+ testLogicBytes(t, ops.Program, ep)
+
+ ep = defaultEvalParamsWithVersion(&txn, 1)
+ testLogicBytes(t, ops.Program, ep,
+ "greater than protocol supported version 1", "greater than protocol supported version 1")
// hack the version and fail on illegal opcode
ops.Program[0] = 0x1
- ep = defaultEvalParamsV1(nil, nil)
- _, err = Eval(ops.Program, ep)
- require.Error(t, err)
- require.Contains(t, err.Error(), "illegal opcode 0x36") // txna
+ ep = defaultEvalParamsWithVersion(&txn, 1)
+ testLogicBytes(t, ops.Program, ep, "illegal opcode 0x36", "illegal opcode 0x36") // txna
}
func TestStackOverflow(t *testing.T) {
@@ -3956,35 +3629,19 @@ func TestApplicationsDisallowOldTeal(t *testing.T) {
partitiontest.PartitionTest(t)
const source = "int 1"
- ep := defaultEvalParams(nil, nil)
txn := makeSampleTxn()
txn.Txn.Type = protocol.ApplicationCallTx
txn.Txn.RekeyTo = basics.Address{}
- txngroup := []transactions.SignedTxn{txn}
- ep.TxnGroup = txngroup
+ ep := defaultEvalParams(&txn)
for v := uint64(0); v < appsEnabledVersion; v++ {
- ops, err := AssembleStringWithVersion(source, v)
- require.NoError(t, err)
-
- err = CheckStateful(ops.Program, ep)
- require.Error(t, err)
- require.Contains(t, err.Error(), fmt.Sprintf("program version must be >= %d", appsEnabledVersion))
-
- _, err = EvalStateful(ops.Program, ep)
- require.Error(t, err)
- require.Contains(t, err.Error(), fmt.Sprintf("program version must be >= %d", appsEnabledVersion))
+ ops := testProg(t, source, v)
+ e := fmt.Sprintf("program version must be >= %d", appsEnabledVersion)
+ testAppBytes(t, ops.Program, ep, e, e)
}
- ops, err := AssembleStringWithVersion(source, appsEnabledVersion)
- require.NoError(t, err)
-
- err = CheckStateful(ops.Program, ep)
- require.NoError(t, err)
-
- _, err = EvalStateful(ops.Program, ep)
- require.NoError(t, err)
+ testApp(t, source, ep)
}
func TestAnyRekeyToOrApplicationRaisesMinTealVersion(t *testing.T) {
@@ -4028,53 +3685,26 @@ func TestAnyRekeyToOrApplicationRaisesMinTealVersion(t *testing.T) {
for ci, cse := range cases {
t.Run(fmt.Sprintf("ci=%d", ci), func(t *testing.T) {
- ep := defaultEvalParams(nil, nil)
- ep.TxnGroup = cse.group
- ep.Txn = &cse.group[0]
+ ep := defaultEvalParams(nil)
+ ep.TxnGroup = transactions.WrapSignedTxnsWithAD(cse.group)
// Computed MinTealVersion should be == validFromVersion
- calc := ComputeMinTealVersion(cse.group)
+ calc := ComputeMinTealVersion(ep.TxnGroup, false)
require.Equal(t, calc, cse.validFromVersion)
// Should fail for all versions < validFromVersion
expected := fmt.Sprintf("program version must be >= %d", cse.validFromVersion)
for v := uint64(0); v < cse.validFromVersion; v++ {
- ops, err := AssembleStringWithVersion(source, v)
- require.NoError(t, err)
-
- err = CheckStateful(ops.Program, ep)
- require.Error(t, err)
- require.Contains(t, err.Error(), expected)
-
- _, err = EvalStateful(ops.Program, ep)
- require.Error(t, err)
- require.Contains(t, err.Error(), expected)
-
- err = Check(ops.Program, ep)
- require.Error(t, err)
- require.Contains(t, err.Error(), expected)
-
- _, err = Eval(ops.Program, ep)
- require.Error(t, err)
- require.Contains(t, err.Error(), expected)
+ ops := testProg(t, source, v)
+ testAppBytes(t, ops.Program, ep, expected, expected)
+ testLogicBytes(t, ops.Program, ep, expected, expected)
}
// Should succeed for all versions >= validFromVersion
for v := cse.validFromVersion; v <= AssemblerMaxVersion; v++ {
- ops, err := AssembleStringWithVersion(source, v)
- require.NoError(t, err)
-
- err = CheckStateful(ops.Program, ep)
- require.NoError(t, err)
-
- _, err = EvalStateful(ops.Program, ep)
- require.NoError(t, err)
-
- err = Check(ops.Program, ep)
- require.NoError(t, err)
-
- _, err = Eval(ops.Program, ep)
- require.NoError(t, err)
+ ops := testProg(t, source, v)
+ testAppBytes(t, ops.Program, ep)
+ testLogicBytes(t, ops.Program, ep)
}
})
}
@@ -4119,7 +3749,7 @@ func TestAllowedOpcodesV2(t *testing.T) {
"gtxn": true,
}
- ep := defaultEvalParams(nil, nil)
+ ep := defaultEvalParams(nil)
cnt := 0
for _, spec := range OpSpecs {
@@ -4128,10 +3758,10 @@ func TestAllowedOpcodesV2(t *testing.T) {
require.True(t, ok, "Missed opcode in the test: %s", spec.Name)
require.Contains(t, source, spec.Name)
ops := testProg(t, source, AssemblerMaxVersion)
- // all opcodes allowed in stateful mode so use CheckStateful/EvalStateful
- err := CheckStateful(ops.Program, ep)
+ // all opcodes allowed in stateful mode so use CheckStateful/EvalContract
+ err := CheckContract(ops.Program, ep)
require.NoError(t, err, source)
- _, err = EvalStateful(ops.Program, ep)
+ _, err = EvalApp(ops.Program, 0, 0, ep)
if spec.Name != "return" {
// "return" opcode always succeeds so ignore it
require.Error(t, err, source)
@@ -4140,18 +3770,8 @@ func TestAllowedOpcodesV2(t *testing.T) {
for v := byte(0); v <= 1; v++ {
ops.Program[0] = v
- err = Check(ops.Program, ep)
- require.Error(t, err, source)
- require.Contains(t, err.Error(), "illegal opcode")
- err = CheckStateful(ops.Program, ep)
- require.Error(t, err, source)
- require.Contains(t, err.Error(), "illegal opcode")
- _, err = Eval(ops.Program, ep)
- require.Error(t, err, source)
- require.Contains(t, err.Error(), "illegal opcode")
- _, err = EvalStateful(ops.Program, ep)
- require.Error(t, err, source)
- require.Contains(t, err.Error(), "illegal opcode")
+ testLogicBytes(t, ops.Program, ep, "illegal opcode", "illegal opcode")
+ testAppBytes(t, ops.Program, ep, "illegal opcode", "illegal opcode")
}
cnt++
}
@@ -4182,43 +3802,27 @@ func TestAllowedOpcodesV3(t *testing.T) {
"pushbytes": `pushbytes "stringsfail?"`,
}
- excluded := map[string]bool{}
-
- ep := defaultEvalParams(nil, nil)
+ ep := defaultEvalParams(nil)
cnt := 0
for _, spec := range OpSpecs {
- if spec.Version == 3 && !excluded[spec.Name] {
+ if spec.Version == 3 {
source, ok := tests[spec.Name]
require.True(t, ok, "Missed opcode in the test: %s", spec.Name)
require.Contains(t, source, spec.Name)
ops := testProg(t, source, AssemblerMaxVersion)
- // all opcodes allowed in stateful mode so use CheckStateful/EvalStateful
- err := CheckStateful(ops.Program, ep)
- require.NoError(t, err, source)
- _, err = EvalStateful(ops.Program, ep)
- require.Error(t, err, source)
- require.NotContains(t, err.Error(), "illegal opcode")
+ // all opcodes allowed in stateful mode so use CheckStateful/EvalContract
+ testAppBytes(t, ops.Program, ep, "REJECT")
for v := byte(0); v <= 1; v++ {
ops.Program[0] = v
- err = Check(ops.Program, ep)
- require.Error(t, err, source)
- require.Contains(t, err.Error(), "illegal opcode")
- err = CheckStateful(ops.Program, ep)
- require.Error(t, err, source)
- require.Contains(t, err.Error(), "illegal opcode")
- _, err = Eval(ops.Program, ep)
- require.Error(t, err, source)
- require.Contains(t, err.Error(), "illegal opcode")
- _, err = EvalStateful(ops.Program, ep)
- require.Error(t, err, source)
- require.Contains(t, err.Error(), "illegal opcode")
+ testLogicBytes(t, ops.Program, ep, "illegal opcode", "illegal opcode")
+ testAppBytes(t, ops.Program, ep, "illegal opcode", "illegal opcode")
}
cnt++
}
}
- require.Equal(t, len(tests), cnt)
+ require.Len(t, tests, cnt)
}
func TestRekeyFailsOnOldVersion(t *testing.T) {
@@ -4227,23 +3831,12 @@ func TestRekeyFailsOnOldVersion(t *testing.T) {
t.Parallel()
for v := uint64(0); v < rekeyingEnabledVersion; v++ {
t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) {
- ops, err := AssembleStringWithVersion(`int 1`, v)
- require.NoError(t, err)
+ ops := testProg(t, `int 1`, v)
var txn transactions.SignedTxn
- txn.Lsig.Logic = ops.Program
txn.Txn.RekeyTo = basics.Address{1, 2, 3, 4}
- sb := strings.Builder{}
- proto := defaultEvalProto()
- ep := defaultEvalParams(&sb, &txn)
- ep.TxnGroup = []transactions.SignedTxn{txn}
- ep.Proto = &proto
- err = Check(ops.Program, ep)
- require.Error(t, err)
- require.Contains(t, err.Error(), fmt.Sprintf("program version must be >= %d", rekeyingEnabledVersion))
- pass, err := Eval(ops.Program, ep)
- require.Error(t, err)
- require.Contains(t, err.Error(), fmt.Sprintf("program version must be >= %d", rekeyingEnabledVersion))
- require.False(t, pass)
+ ep := defaultEvalParams(&txn)
+ e := fmt.Sprintf("program version must be >= %d", rekeyingEnabledVersion)
+ testLogicBytes(t, ops.Program, ep, e, e)
})
}
}
@@ -4268,7 +3861,7 @@ func testEvaluation(t *testing.T, program string, introduced uint64, tester eval
t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) {
t.Helper()
if v < introduced {
- testProg(t, obfuscate(program), v, expect{0, "...was introduced..."})
+ testProg(t, obfuscate(program), v, Expect{0, "...was introduced..."})
return
}
ops := testProg(t, program, v)
@@ -4277,21 +3870,20 @@ func testEvaluation(t *testing.T, program string, introduced uint64, tester eval
// EvalParams, so try all forward versions.
for lv := v; lv <= AssemblerMaxVersion; lv++ {
t.Run(fmt.Sprintf("lv=%d", lv), func(t *testing.T) {
- sb := strings.Builder{}
- err := Check(ops.Program, defaultEvalParamsWithVersion(&sb, nil, lv))
+ t.Helper()
+ var txn transactions.SignedTxn
+ txn.Lsig.Logic = ops.Program
+ ep := defaultEvalParamsWithVersion(&txn, lv)
+ err := CheckSignature(0, ep)
if err != nil {
- t.Log(hex.EncodeToString(ops.Program))
- t.Log(sb.String())
+ t.Log(ep.Trace.String())
}
require.NoError(t, err)
- var txn transactions.SignedTxn
- txn.Lsig.Logic = ops.Program
- sb = strings.Builder{}
- pass, err := Eval(ops.Program, defaultEvalParamsWithVersion(&sb, &txn, lv))
+ ep = defaultEvalParamsWithVersion(&txn, lv)
+ pass, err := EvalSignature(0, ep)
ok := tester(pass, err)
if !ok {
- t.Log(hex.EncodeToString(ops.Program))
- t.Log(sb.String())
+ t.Log(ep.Trace.String())
t.Log(err)
}
require.True(t, ok)
@@ -4403,6 +3995,8 @@ func TestBytes(t *testing.T) {
// it fails to copy).
testAccepts(t, `byte "john"; dup; int 2; int 105; setbyte; pop; byte "john"; ==`, 3)
testAccepts(t, `byte "jo"; byte "hn"; concat; dup; int 2; int 105; setbyte; pop; byte "john"; ==`, 3)
+
+ testAccepts(t, `byte "john"; byte "john"; ==`, 1)
}
func TestMethod(t *testing.T) {
@@ -4727,6 +4321,16 @@ func TestBytesMath(t *testing.T) {
// Even 128 byte outputs are ok
testAccepts(t, fmt.Sprintf("byte 0x%s; byte 0x%s; b*; len; int 128; ==", effs, effs), 4)
+
+ testAccepts(t, "byte 0x00; bsqrt; byte 0x; ==; return", 6)
+ testAccepts(t, "byte 0x01; bsqrt; byte 0x01; ==; return", 6)
+ testAccepts(t, "byte 0x10; bsqrt; byte 0x04; ==; return", 6)
+ testAccepts(t, "byte 0x11; bsqrt; byte 0x04; ==; return", 6)
+ testAccepts(t, "byte 0xffffff; bsqrt; len; int 2; ==; return", 6)
+ // 64 byte long inputs are accepted, even if they produce longer outputs
+ testAccepts(t, fmt.Sprintf("byte 0x%s; bsqrt; len; int 32; ==", effs), 6)
+ // 65 byte inputs are not ok.
+ testPanics(t, fmt.Sprintf("byte 0x%s00; bsqrt; pop; int 1", effs), 6)
}
func TestBytesCompare(t *testing.T) {
@@ -4799,17 +4403,12 @@ func TestLog(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()
- proto := defaultEvalProtoWithVersion(LogicVersion)
- txn := transactions.SignedTxn{
- Txn: transactions.Transaction{
- Type: protocol.ApplicationCallTx,
- },
- }
- ledger := logictest.MakeLedger(nil)
+ var txn transactions.SignedTxn
+ txn.Txn.Type = protocol.ApplicationCallTx
+ ledger := MakeLedger(nil)
ledger.NewApp(txn.Txn.Receiver, 0, basics.AppParams{})
- sb := strings.Builder{}
- ep := defaultEvalParams(&sb, &txn)
- ep.Proto = &proto
+ ep := defaultEvalParams(&txn)
+ ep.Proto = makeTestProtoV(LogicVersion)
ep.Ledger = ledger
testCases := []struct {
source string
@@ -4837,21 +4436,14 @@ func TestLog(t *testing.T) {
},
}
- //track expected number of logs in cx.Logs
+ //track expected number of logs in cx.EvalDelta.Logs
for i, s := range testCases {
- ops := testProg(t, s.source, AssemblerMaxVersion)
-
- err := CheckStateful(ops.Program, ep)
- require.NoError(t, err, s)
-
- pass, cx, err := EvalStatefulCx(ops.Program, ep)
- require.NoError(t, err)
- require.True(t, pass)
- require.Len(t, cx.Logs, s.loglen)
+ delta := testApp(t, s.source, ep)
+ require.Len(t, delta.Logs, s.loglen)
if i == len(testCases)-1 {
- require.Equal(t, strings.Repeat("a", MaxLogSize), cx.Logs[0])
+ require.Equal(t, strings.Repeat("a", MaxLogSize), delta.Logs[0])
} else {
- for _, l := range cx.Logs {
+ for _, l := range delta.Logs {
require.Equal(t, "a logging message", l)
}
}
@@ -4901,21 +4493,12 @@ func TestLog(t *testing.T) {
}
for _, c := range failCases {
- ops := testProg(t, c.source, AssemblerMaxVersion)
-
- err := CheckStateful(ops.Program, ep)
- require.NoError(t, err, c)
-
- var pass bool
switch c.runMode {
case runModeApplication:
- pass, err = EvalStateful(ops.Program, ep)
+ testApp(t, c.source, ep, c.errContains)
default:
- pass, err = Eval(ops.Program, ep)
-
+ testLogic(t, c.source, AssemblerMaxVersion, ep, c.errContains, c.errContains)
}
- require.Contains(t, err.Error(), c.errContains)
- require.False(t, pass)
}
}
@@ -4936,184 +4519,122 @@ func TestPcDetails(t *testing.T) {
for i, test := range tests {
t.Run(fmt.Sprintf("i=%d", i), func(t *testing.T) {
ops := testProg(t, test.source, LogicVersion)
- txn := makeSampleTxn()
- txgroup := makeSampleTxnGroup(txn)
- txn.Lsig.Logic = ops.Program
- sb := strings.Builder{}
- ep := defaultEvalParams(&sb, &txn)
- ep.TxnGroup = txgroup
-
- var cx EvalContext
- cx.EvalParams = ep
- cx.runModeFlags = runModeSignature
+ ep, _, _ := makeSampleEnv()
- pass, err := eval(ops.Program, &cx)
+ pass, cx, err := EvalContract(ops.Program, 0, 0, ep)
require.Error(t, err)
require.False(t, pass)
+ assert.Equal(t, test.pc, cx.pc, ep.Trace.String())
+
pc, det := cx.PcDetails()
- require.Equal(t, test.pc, pc)
- require.Equal(t, test.det, det)
+ assert.Equal(t, test.pc, pc)
+ assert.Equal(t, test.det, det)
})
}
}
var minB64DecodeVersion uint64 = 6
-type b64DecodeTestCase struct {
- Encoded string
- IsURL bool
- HasExtraNLs bool
- Decoded string
- Error error
-}
+func TestOpBase64Decode(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
-var testCases = []b64DecodeTestCase{
- {"TU9CWS1ESUNLOwoKb3IsIFRIRSBXSEFMRS4KCgpCeSBIZXJtYW4gTWVsdmlsbGU=",
- false,
- false,
- `MOBY-DICK;
+ testCases := []struct {
+ encoded string
+ alph string
+ decoded string
+ error string
+ }{
+ {"TU9CWS1ESUNLOwoKb3IsIFRIRSBXSEFMRS4KCgpCeSBIZXJtYW4gTWVsdmlsbGU=",
+ "StdEncoding",
+ `MOBY-DICK;
or, THE WHALE.
-By Herman Melville`,
- nil,
- },
- {"TU9CWS1ESUNLOwoKb3IsIFRIRSBXSEFMRS4KCgpCeSBIZXJtYW4gTWVsdmlsbGU=",
- true,
- false,
- `MOBY-DICK;
+By Herman Melville`, "",
+ },
+ {"TU9CWS1ESUNLOwoKb3IsIFRIRSBXSEFMRS4KCgpCeSBIZXJtYW4gTWVsdmlsbGU=",
+ "URLEncoding",
+ `MOBY-DICK;
or, THE WHALE.
-By Herman Melville`,
- nil,
- },
- {"YWJjMTIzIT8kKiYoKSctPUB+", false, false, "abc123!?$*&()'-=@~", nil},
- {"YWJjMTIzIT8kKiYoKSctPUB-", true, false, "abc123!?$*&()'-=@~", nil},
- {"YWJjMTIzIT8kKiYoKSctPUB+", true, false, "", base64.CorruptInputError(23)},
- {"YWJjMTIzIT8kKiYoKSctPUB-", false, false, "", base64.CorruptInputError(23)},
-
- // try extra ='s and various whitespace:
- {"", false, false, "", nil},
- {"", true, false, "", nil},
- {"=", false, true, "", base64.CorruptInputError(0)},
- {"=", true, true, "", base64.CorruptInputError(0)},
- {" ", false, true, "", base64.CorruptInputError(0)},
- {" ", true, true, "", base64.CorruptInputError(0)},
- {"\t", false, true, "", base64.CorruptInputError(0)},
- {"\t", true, true, "", base64.CorruptInputError(0)},
- {"\r", false, true, "", nil},
- {"\r", true, true, "", nil},
- {"\n", false, true, "", nil},
- {"\n", true, true, "", nil},
-
- {"YWJjMTIzIT8kKiYoKSctPUB+\n", false, true, "abc123!?$*&()'-=@~", nil},
- {"YWJjMTIzIT8kKiYoKSctPUB-\n", true, true, "abc123!?$*&()'-=@~", nil},
- {"YWJjMTIzIT8kK\riYoKSctPUB+\n", false, true, "abc123!?$*&()'-=@~", nil},
- {"YWJjMTIzIT8kK\riYoKSctPUB-\n", true, true, "abc123!?$*&()'-=@~", nil},
- {"\n\rYWJjMTIzIT8\rkKiYoKSctPUB+\n", false, true, "abc123!?$*&()'-=@~", nil},
- {"\n\rYWJjMTIzIT8\rkKiYoKSctPUB-\n", true, true, "abc123!?$*&()'-=@~", nil},
-
- // padding and extra legal whitespace
- {"SQ==", false, false, "I", nil},
- {"SQ==", true, false, "I", nil},
- {"\rS\r\nQ=\n=\r\r\n", false, true, "I", nil},
- {"\rS\r\nQ=\n=\r\r\n", true, true, "I", nil},
-
- // Padding necessary? - Yes it is! And exactly the expected place and amount.
- {"SQ==", false, false, "I", nil},
- {"SQ==", true, false, "I", nil},
- {"S=Q=", false, false, "", base64.CorruptInputError(1)},
- {"S=Q=", true, false, "", base64.CorruptInputError(1)},
- {"=SQ=", false, false, "", base64.CorruptInputError(0)},
- {"=SQ=", true, false, "", base64.CorruptInputError(0)},
- {"SQ", false, false, "", base64.CorruptInputError(0)},
- {"SQ", true, false, "", base64.CorruptInputError(0)},
- {"SQ=", false, false, "", base64.CorruptInputError(3)},
- {"SQ=", true, false, "", base64.CorruptInputError(3)},
- {"SQ===", false, false, "", base64.CorruptInputError(4)},
- {"SQ===", true, false, "", base64.CorruptInputError(4)},
-}
-
-func TestBase64DecodeFunc(t *testing.T) {
- partitiontest.PartitionTest(t)
- t.Parallel()
-
- for _, testCase := range testCases {
- encoding := base64.StdEncoding
- if testCase.IsURL {
- encoding = base64.URLEncoding
- }
- // sanity check:
- if testCase.Error == nil && !testCase.HasExtraNLs {
- require.Equal(t, testCase.Encoded, encoding.EncodeToString([]byte(testCase.Decoded)))
- }
+By Herman Melville`, "",
+ },
- decoded, err := base64Decode([]byte(testCase.Encoded), encoding)
- require.Equal(t, testCase.Error, err, fmt.Sprintf("Error (%s): case decode [%s] -> [%s]", err, testCase.Encoded, testCase.Decoded))
- require.Equal(t, []byte(testCase.Decoded), decoded)
+ // Test that a string that doesn't need padding can't have it
+ {"cGFk", "StdEncoding", "pad", ""},
+ {"cGFk=", "StdEncoding", "pad", "input byte 4"},
+ {"cGFk==", "StdEncoding", "pad", "input byte 4"},
+ {"cGFk===", "StdEncoding", "pad", "input byte 4"},
+ // Ensures that even correct padding is illegal if not needed
+ {"cGFk====", "StdEncoding", "pad", "input byte 4"},
+
+ // Test that padding must be present to make len = 0 mod 4.
+ {"bm9wYWQ=", "StdEncoding", "nopad", ""},
+ {"bm9wYWQ", "StdEncoding", "nopad", "illegal"},
+ {"bm9wYWQ==", "StdEncoding", "nopad", "illegal"},
+
+ {"YWJjMTIzIT8kKiYoKSctPUB+", "StdEncoding", "abc123!?$*&()'-=@~", ""},
+ {"YWJjMTIzIT8kKiYoKSctPUB+", "StdEncoding", "abc123!?$*&()'-=@~", ""},
+ {"YWJjMTIzIT8kKiYoKSctPUB-", "URLEncoding", "abc123!?$*&()'-=@~", ""},
+ {"YWJjMTIzIT8kKiYoKSctPUB+", "URLEncoding", "", "input byte 23"},
+ {"YWJjMTIzIT8kKiYoKSctPUB-", "StdEncoding", "", "input byte 23"},
+
+ // try extra ='s and various whitespace:
+ {"", "StdEncoding", "", ""},
+ {"", "URLEncoding", "", ""},
+ {"=", "StdEncoding", "", "byte 0"},
+ {"=", "URLEncoding", "", "byte 0"},
+ {" ", "StdEncoding", "", "byte 0"},
+ {" ", "URLEncoding", "", "byte 0"},
+ {"\t", "StdEncoding", "", "byte 0"},
+ {"\t", "URLEncoding", "", "byte 0"},
+ {"\r", "StdEncoding", "", ""},
+ {"\r", "URLEncoding", "", ""},
+ {"\n", "StdEncoding", "", ""},
+ {"\n", "URLEncoding", "", ""},
+
+ {"YWJjMTIzIT8kKiYoKSctPUB+\n", "StdEncoding", "abc123!?$*&()'-=@~", ""},
+ {"YWJjMTIzIT8kKiYoKSctPUB-\n", "URLEncoding", "abc123!?$*&()'-=@~", ""},
+ {"YWJjMTIzIT8kK\riYoKSctPUB+\n", "StdEncoding", "abc123!?$*&()'-=@~", ""},
+ {"YWJjMTIzIT8kK\riYoKSctPUB-\n", "URLEncoding", "abc123!?$*&()'-=@~", ""},
+ {"\n\rYWJjMTIzIT8\rkKiYoKSctPUB+\n", "StdEncoding", "abc123!?$*&()'-=@~", ""},
+ {"\n\rYWJjMTIzIT8\rkKiYoKSctPUB-\n", "URLEncoding", "abc123!?$*&()'-=@~", ""},
+
+ // padding and extra legal whitespace
+ {"SQ==", "StdEncoding", "I", ""},
+ {"SQ==", "URLEncoding", "I", ""},
+ {"\rS\r\nQ=\n=\r\r\n", "StdEncoding", "I", ""},
+ {"\rS\r\nQ=\n=\r\r\n", "URLEncoding", "I", ""},
+
+ // Padding necessary? - Yes it is! And exactly the expected place and amount.
+ {"SQ==", "StdEncoding", "I", ""},
+ {"SQ==", "URLEncoding", "I", ""},
+ {"S=Q=", "StdEncoding", "", "byte 1"},
+ {"S=Q=", "URLEncoding", "", "byte 1"},
+ {"=SQ=", "StdEncoding", "", "byte 0"},
+ {"=SQ=", "URLEncoding", "", "byte 0"},
+ {"SQ", "StdEncoding", "", "byte 0"},
+ {"SQ", "URLEncoding", "", "byte 0"},
+ {"SQ=", "StdEncoding", "", "byte 3"},
+ {"SQ=", "URLEncoding", "", "byte 3"},
+ {"SQ===", "StdEncoding", "", "byte 4"},
+ {"SQ===", "URLEncoding", "", "byte 4"},
}
-}
-
-type b64DecodeTestArgs struct {
- Raw []byte
- Encoded []byte
- IsURL bool
- Program []byte
-}
-func b64TestDecodeAssembleWithArgs(t *testing.T) []b64DecodeTestArgs {
- sourceTmpl := `#pragma version %d
-arg 0
-arg 1
-base64_decode %s
-==`
- args := []b64DecodeTestArgs{}
- for _, testCase := range testCases {
- if testCase.Error == nil {
- field := "StdEncoding"
- if testCase.IsURL {
- field = "URLEncoding"
- }
- source := fmt.Sprintf(sourceTmpl, minB64DecodeVersion, field)
- ops, err := AssembleStringWithVersion(source, minB64DecodeVersion)
- require.NoError(t, err)
+ template := `byte 0x%s; byte 0x%s; base64_decode %s; ==`
+ for _, tc := range testCases {
+ source := fmt.Sprintf(template, hex.EncodeToString([]byte(tc.decoded)), hex.EncodeToString([]byte(tc.encoded)), tc.alph)
- arg := b64DecodeTestArgs{
- Raw: []byte(testCase.Decoded),
- Encoded: []byte(testCase.Encoded),
- IsURL: testCase.IsURL,
- Program: ops.Program,
- }
- args = append(args, arg)
- }
- }
- return args
-}
-
-func b64TestDecodeEval(tb testing.TB, args []b64DecodeTestArgs) {
- for _, data := range args {
- var txn transactions.SignedTxn
- txn.Lsig.Logic = data.Program
- txn.Lsig.Args = [][]byte{data.Raw[:], data.Encoded[:]}
- ep := defaultEvalParams(&strings.Builder{}, &txn)
- pass, err := Eval(data.Program, ep)
- if err != nil {
- require.NoError(tb, err)
- }
- if !pass {
- fmt.Printf("FAILING WITH data = %#v", data)
- require.True(tb, pass)
+ if tc.error == "" {
+ testAccepts(t, source, minB64DecodeVersion)
+ } else {
+ err := testPanics(t, source, minB64DecodeVersion)
+ require.Contains(t, err.Error(), tc.error)
}
}
}
-
-func TestOpBase64Decode(t *testing.T) {
- partitiontest.PartitionTest(t)
- t.Parallel()
- args := b64TestDecodeAssembleWithArgs(t)
- b64TestDecodeEval(t, args)
-}