diff options
author | John Lee <64482439+algojohnlee@users.noreply.github.com> | 2021-08-24 20:19:11 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-08-24 20:19:11 -0400 |
commit | d1aca92d36fd2a69d44338e566fc76214a089778 (patch) | |
tree | 9a9ded7c8b1f5edd22bdae8ed8e7de26ec0949ba | |
parent | 1f0544885dccab47b0ed06e09b48a2aa8c8e70e7 (diff) | |
parent | f2be951910d5c6d227e7c45b70b5634b38c98ba2 (diff) |
Merge pull request #2800 from algojack/jack/relbeta2.10.1v2.10.1-beta
go-algorand 2.10.1-beta
-rw-r--r-- | buildnumber.dat | 2 | ||||
-rw-r--r-- | data/transactions/logic/assembler.go | 61 | ||||
-rw-r--r-- | data/transactions/logic/assembler_test.go | 4 | ||||
-rw-r--r-- | data/transactions/logic/debugger.go | 8 | ||||
-rw-r--r-- | data/transactions/logic/eval.go | 87 | ||||
-rw-r--r-- | data/transactions/logic/evalStateful_test.go | 83 | ||||
-rw-r--r-- | data/transactions/logic/fields.go | 167 | ||||
-rw-r--r-- | data/transactions/logic/fields_test.go | 66 |
8 files changed, 318 insertions, 160 deletions
diff --git a/buildnumber.dat b/buildnumber.dat index 573541ac9..d00491fd7 100644 --- a/buildnumber.dat +++ b/buildnumber.dat @@ -1 +1 @@ -0 +1 diff --git a/data/transactions/logic/assembler.go b/data/transactions/logic/assembler.go index 6b8f5793b..c5e38d632 100644 --- a/data/transactions/logic/assembler.go +++ b/data/transactions/logic/assembler.go @@ -798,7 +798,7 @@ func assembleTxn(ops *OpStream, spec *OpSpec, args []string) error { } ops.pending.WriteByte(spec.Opcode) ops.pending.WriteByte(uint8(fs.field)) - ops.returns(TxnFieldTypes[fs.field]) + ops.returns(fs.ftype) return nil } @@ -840,7 +840,7 @@ func assembleTxna(ops *OpStream, spec *OpSpec, args []string) error { ops.pending.WriteByte(spec.Opcode) ops.pending.WriteByte(uint8(fs.field)) ops.pending.WriteByte(uint8(arrayFieldIdx)) - ops.returns(TxnFieldTypes[fs.field]) + ops.returns(fs.ftype) return nil } @@ -871,7 +871,7 @@ func assembleGtxn(ops *OpStream, spec *OpSpec, args []string) error { ops.pending.WriteByte(spec.Opcode) ops.pending.WriteByte(uint8(slot)) ops.pending.WriteByte(uint8(fs.field)) - ops.returns(TxnFieldTypes[fs.field]) + ops.returns(fs.ftype) return nil } @@ -921,7 +921,7 @@ func assembleGtxna(ops *OpStream, spec *OpSpec, args []string) error { ops.pending.WriteByte(uint8(slot)) ops.pending.WriteByte(uint8(fs.field)) ops.pending.WriteByte(uint8(arrayFieldIdx)) - ops.returns(TxnFieldTypes[fs.field]) + ops.returns(fs.ftype) return nil } @@ -947,7 +947,7 @@ func assembleGtxns(ops *OpStream, spec *OpSpec, args []string) error { ops.pending.WriteByte(spec.Opcode) ops.pending.WriteByte(uint8(fs.field)) - ops.returns(TxnFieldTypes[fs.field]) + ops.returns(fs.ftype) return nil } @@ -976,7 +976,7 @@ func assembleGtxnsa(ops *OpStream, spec *OpSpec, args []string) error { ops.pending.WriteByte(spec.Opcode) ops.pending.WriteByte(uint8(fs.field)) ops.pending.WriteByte(uint8(arrayFieldIdx)) - ops.returns(TxnFieldTypes[fs.field]) + ops.returns(fs.ftype) return nil } @@ -989,15 +989,15 @@ func assembleGlobal(ops *OpStream, spec *OpSpec, args []string) error { return ops.errorf("%s unknown field: %#v", spec.Name, args[0]) } if fs.version > ops.Version { - // no return here. we may as well continue to maintain typestack - ops.errorf("global %s available in version %d. Missed #pragma version?", args[0], fs.version) + //nolint:errcheck // we continue to maintain typestack + ops.errorf("%s %s available in version %d. Missed #pragma version?", spec.Name, args[0], fs.version) } - val := fs.gfield + val := fs.field ops.pending.WriteByte(spec.Opcode) ops.pending.WriteByte(uint8(val)) - ops.trace("%s (%s)", GlobalFieldNames[val], GlobalFieldTypes[val].String()) - ops.returns(GlobalFieldTypes[val]) + ops.trace("%s (%s)", fs.field.String(), fs.ftype.String()) + ops.returns(fs.ftype) return nil } @@ -1005,13 +1005,20 @@ func assembleAssetHolding(ops *OpStream, spec *OpSpec, args []string) error { if len(args) != 1 { return ops.errorf("%s expects one argument", spec.Name) } - val, ok := assetHoldingFields[args[0]] + fs, ok := assetHoldingFieldSpecByName[args[0]] if !ok { - return ops.errorf("%s unknown arg: %#v", spec.Name, args[0]) + return ops.errorf("%s unknown field: %#v", spec.Name, args[0]) + } + if fs.version > ops.Version { + //nolint:errcheck // we continue to maintain typestack + ops.errorf("%s %s available in version %d. Missed #pragma version?", spec.Name, args[0], fs.version) } + + val := fs.field ops.pending.WriteByte(spec.Opcode) ops.pending.WriteByte(uint8(val)) - ops.returns(AssetHoldingFieldTypes[val], StackUint64) + ops.trace("%s (%s)", fs.field.String(), fs.ftype.String()) + ops.returns(fs.ftype, StackUint64) return nil } @@ -1019,13 +1026,20 @@ func assembleAssetParams(ops *OpStream, spec *OpSpec, args []string) error { if len(args) != 1 { return ops.errorf("%s expects one argument", spec.Name) } - val, ok := assetParamsFields[args[0]] + fs, ok := assetParamsFieldSpecByName[args[0]] if !ok { - return ops.errorf("%s unknown arg: %#v", spec.Name, args[0]) + return ops.errorf("%s unknown field: %#v", spec.Name, args[0]) + } + if fs.version > ops.Version { + //nolint:errcheck // we continue to maintain typestack + ops.errorf("%s %s available in version %d. Missed #pragma version?", spec.Name, args[0], fs.version) } + + val := fs.field ops.pending.WriteByte(spec.Opcode) ops.pending.WriteByte(uint8(val)) - ops.returns(AssetParamsFieldTypes[val], StackUint64) + ops.trace("%s (%s)", fs.field.String(), fs.ftype.String()) + ops.returns(fs.ftype, StackUint64) return nil } @@ -1033,13 +1047,20 @@ func assembleAppParams(ops *OpStream, spec *OpSpec, args []string) error { if len(args) != 1 { return ops.errorf("%s expects one argument", spec.Name) } - val, ok := appParamsFields[args[0]] + fs, ok := appParamsFieldSpecByName[args[0]] if !ok { - return ops.errorf("%s unknown arg: %#v", spec.Name, args[0]) + return ops.errorf("%s unknown field: %#v", spec.Name, args[0]) } + if fs.version > ops.Version { + //nolint:errcheck // we continue to maintain typestack + ops.errorf("%s %s available in version %d. Missed #pragma version?", spec.Name, args[0], fs.version) + } + + val := fs.field ops.pending.WriteByte(spec.Opcode) ops.pending.WriteByte(uint8(val)) - ops.returns(AppParamsFieldTypes[val], StackUint64) + ops.trace("%s (%s)", fs.field.String(), fs.ftype.String()) + ops.returns(fs.ftype, StackUint64) return nil } diff --git a/data/transactions/logic/assembler_test.go b/data/transactions/logic/assembler_test.go index a8cd151f7..37c6333cd 100644 --- a/data/transactions/logic/assembler_test.go +++ b/data/transactions/logic/assembler_test.go @@ -1578,13 +1578,13 @@ func TestAssembleAsset(t *testing.T) { testProg(t, "int 1; int 1; asset_holding_get ABC 1", v, expect{3, "asset_holding_get expects one argument"}) testProg(t, "int 1; int 1; asset_holding_get ABC", v, - expect{3, "asset_holding_get unknown arg: \"ABC\""}) + expect{3, "asset_holding_get unknown field: \"ABC\""}) testProg(t, "byte 0x1234; asset_params_get ABC 1", v, expect{2, "asset_params_get ABC 1 arg 0 wanted type uint64..."}) testLine(t, "asset_params_get ABC 1", v, "asset_params_get expects one argument") - testLine(t, "asset_params_get ABC", v, "asset_params_get unknown arg: \"ABC\"") + testLine(t, "asset_params_get ABC", v, "asset_params_get unknown field: \"ABC\"") } } diff --git a/data/transactions/logic/debugger.go b/data/transactions/logic/debugger.go index 021a53faf..b591aad01 100644 --- a/data/transactions/logic/debugger.go +++ b/data/transactions/logic/debugger.go @@ -103,13 +103,13 @@ func makeDebugState(cx *evalContext) DebugState { Proto: cx.Proto, } - globals := make([]basics.TealValue, len(GlobalFieldNames)) - for fieldIdx := range GlobalFieldNames { - sv, err := cx.globalFieldToStack(GlobalField(fieldIdx)) + globals := make([]basics.TealValue, len(globalFieldSpecs)) + for _, fs := range globalFieldSpecs { + sv, err := cx.globalFieldToValue(fs) if err != nil { sv = stackValue{Bytes: []byte(err.Error())} } - globals[fieldIdx] = stackValueToTealValue(&sv) + globals[fs.field] = stackValueToTealValue(&sv) } ds.Globals = globals diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index ced3baed3..ac06dc701 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -1751,27 +1751,25 @@ func opUncover(cx *evalContext) { cx.stack[topIdx] = sv } -func (cx *evalContext) assetHoldingEnumToValue(holding *basics.AssetHolding, field uint64) (sv stackValue, err error) { - switch AssetHoldingField(field) { +func (cx *evalContext) assetHoldingToValue(holding *basics.AssetHolding, fs assetHoldingFieldSpec) (sv stackValue, err error) { + switch fs.field { case AssetBalance: sv.Uint = holding.Amount case AssetFrozen: sv.Uint = boolToUint(holding.Frozen) default: - err = fmt.Errorf("invalid asset holding field %d", field) + err = fmt.Errorf("invalid asset_holding_get field %d", fs.field) return } - assetHoldingField := AssetHoldingField(field) - assetHoldingFieldType := AssetHoldingFieldTypes[assetHoldingField] - if !typecheck(assetHoldingFieldType, sv.argType()) { - err = fmt.Errorf("%s expected field type is %s but got %s", assetHoldingField.String(), assetHoldingFieldType.String(), sv.argType().String()) + if !typecheck(fs.ftype, sv.argType()) { + err = fmt.Errorf("%s expected field type is %s but got %s", fs.field.String(), fs.ftype.String(), sv.argType().String()) } return } -func (cx *evalContext) assetParamsEnumToValue(params *basics.AssetParams, creator basics.Address, field uint64) (sv stackValue, err error) { - switch AssetParamsField(field) { +func (cx *evalContext) assetParamsToValue(params *basics.AssetParams, creator basics.Address, fs assetParamsFieldSpec) (sv stackValue, err error) { + switch fs.field { case AssetTotal: sv.Uint = params.Total case AssetDecimals: @@ -1797,20 +1795,18 @@ func (cx *evalContext) assetParamsEnumToValue(params *basics.AssetParams, creato case AssetCreator: sv.Bytes = creator[:] default: - err = fmt.Errorf("invalid asset params field %d", field) + err = fmt.Errorf("invalid asset_params_get field %d", fs.field) return } - assetParamsField := AssetParamsField(field) - assetParamsFieldType := AssetParamsFieldTypes[assetParamsField] - if !typecheck(assetParamsFieldType, sv.argType()) { - err = fmt.Errorf("%s expected field type is %s but got %s", assetParamsField.String(), assetParamsFieldType.String(), sv.argType().String()) + if !typecheck(fs.ftype, sv.argType()) { + err = fmt.Errorf("%s expected field type is %s but got %s", fs.field.String(), fs.ftype.String(), sv.argType().String()) } return } -func (cx *evalContext) appParamsEnumToValue(params *basics.AppParams, creator basics.Address, field uint64) (sv stackValue, err error) { - switch AppParamsField(field) { +func (cx *evalContext) appParamsToValue(params *basics.AppParams, creator basics.Address, fs appParamsFieldSpec) (sv stackValue, err error) { + switch fs.field { case AppApprovalProgram: sv.Bytes = params.ApprovalProgram[:] case AppClearStateProgram: @@ -1828,14 +1824,12 @@ func (cx *evalContext) appParamsEnumToValue(params *basics.AppParams, creator ba case AppCreator: sv.Bytes = creator[:] default: - err = fmt.Errorf("invalid app params field %d", field) + err = fmt.Errorf("invalid app_params_get field %d", fs.field) return } - appParamsField := AppParamsField(field) - appParamsFieldType := AppParamsFieldTypes[appParamsField] - if !typecheck(appParamsFieldType, sv.argType()) { - err = fmt.Errorf("%s expected field type is %s but got %s", appParamsField.String(), appParamsFieldType.String(), sv.argType().String()) + if !typecheck(fs.ftype, sv.argType()) { + err = fmt.Errorf("%s expected field type is %s but got %s", fs.field.String(), fs.ftype.String(), sv.argType().String()) } return } @@ -2285,8 +2279,8 @@ func (cx *evalContext) getCreatorAddress() ([]byte, error) { var zeroAddress basics.Address -func (cx *evalContext) globalFieldToStack(field GlobalField) (sv stackValue, err error) { - switch field { +func (cx *evalContext) globalFieldToValue(fs globalFieldSpec) (sv stackValue, err error) { + switch fs.field { case MinTxnFee: sv.Uint = cx.Proto.MinTxnFee case MinBalance: @@ -2308,17 +2302,21 @@ func (cx *evalContext) globalFieldToStack(field GlobalField) (sv stackValue, err case CreatorAddress: sv.Bytes, err = cx.getCreatorAddress() default: - err = fmt.Errorf("invalid global[%d]", field) + err = fmt.Errorf("invalid global field %d", fs.field) + } + + if !typecheck(fs.ftype, sv.argType()) { + err = fmt.Errorf("%s expected field type is %s but got %s", fs.field.String(), fs.ftype.String(), sv.argType().String()) } + return sv, err } func opGlobal(cx *evalContext) { - gindex := uint64(cx.program[cx.pc+1]) - globalField := GlobalField(gindex) + globalField := GlobalField(cx.program[cx.pc+1]) fs, ok := globalFieldSpecByField[globalField] if !ok || fs.version > cx.version { - cx.err = fmt.Errorf("invalid global[%d]", globalField) + cx.err = fmt.Errorf("invalid global field %d", globalField) return } if (cx.runModeFlags & fs.mode) == 0 { @@ -2326,18 +2324,12 @@ func opGlobal(cx *evalContext) { return } - sv, err := cx.globalFieldToStack(globalField) + sv, err := cx.globalFieldToValue(fs) if err != nil { cx.err = err return } - globalFieldType := GlobalFieldTypes[globalField] - if !typecheck(globalFieldType, sv.argType()) { - cx.err = fmt.Errorf("%s expected field type is %s but got %s", globalField.String(), globalFieldType.String(), sv.argType().String()) - return - } - cx.stack = append(cx.stack, sv) } @@ -3082,7 +3074,12 @@ func opAssetHoldingGet(cx *evalContext) { return } - fieldIdx := uint64(cx.program[cx.pc+1]) + holdingField := AssetHoldingField(cx.program[cx.pc+1]) + fs, ok := assetHoldingFieldSpecByField[holdingField] + if !ok || fs.version > cx.version { + cx.err = fmt.Errorf("invalid asset_holding_get field %d", holdingField) + return + } addr, _, err := accountReference(cx, cx.stack[prev]) if err != nil { @@ -3101,7 +3098,7 @@ func opAssetHoldingGet(cx *evalContext) { if holding, err := cx.Ledger.AssetHolding(addr, asset); err == nil { // the holding exist, read the value exist = 1 - value, err = cx.assetHoldingEnumToValue(&holding, fieldIdx) + value, err = cx.assetHoldingToValue(&holding, fs) if err != nil { cx.err = err return @@ -3120,7 +3117,12 @@ func opAssetParamsGet(cx *evalContext) { return } - paramIdx := uint64(cx.program[cx.pc+1]) + paramField := AssetParamsField(cx.program[cx.pc+1]) + fs, ok := assetParamsFieldSpecByField[paramField] + if !ok || fs.version > cx.version { + cx.err = fmt.Errorf("invalid asset_params_get field %d", paramField) + return + } asset, err := asaReference(cx, cx.stack[last].Uint, true) if err != nil { @@ -3133,7 +3135,7 @@ func opAssetParamsGet(cx *evalContext) { if params, creator, err := cx.Ledger.AssetParams(asset); err == nil { // params exist, read the value exist = 1 - value, err = cx.assetParamsEnumToValue(¶ms, creator, paramIdx) + value, err = cx.assetParamsToValue(¶ms, creator, fs) if err != nil { cx.err = err return @@ -3152,7 +3154,12 @@ func opAppParamsGet(cx *evalContext) { return } - paramIdx := uint64(cx.program[cx.pc+1]) + paramField := AppParamsField(cx.program[cx.pc+1]) + fs, ok := appParamsFieldSpecByField[paramField] + if !ok || fs.version > cx.version { + cx.err = fmt.Errorf("invalid app_params_get field %d", paramField) + return + } app, err := appReference(cx, cx.stack[last].Uint, true) if err != nil { @@ -3165,7 +3172,7 @@ func opAppParamsGet(cx *evalContext) { if params, creator, err := cx.Ledger.AppParams(app); err == nil { // params exist, read the value exist = 1 - value, err = cx.appParamsEnumToValue(¶ms, creator, paramIdx) + value, err = cx.appParamsToValue(¶ms, creator, fs) if err != nil { cx.err = err return diff --git a/data/transactions/logic/evalStateful_test.go b/data/transactions/logic/evalStateful_test.go index d1c53a34a..f782fcce9 100644 --- a/data/transactions/logic/evalStateful_test.go +++ b/data/transactions/logic/evalStateful_test.go @@ -1173,7 +1173,7 @@ int 4141 testApp(t, strings.Replace(text, "int 1 // ForeignApps index", "global CurrentApplicationID", -1), now) } -const assetsTestProgram = `int 0//account +const assetsTestTemplate = `int 0//account int 55 asset_holding_get AssetBalance ! @@ -1270,33 +1270,51 @@ bnz ok error: err ok: +%s +int 1 +` + +const v5extras = ` int 0//params asset_params_get AssetCreator pop txn Sender == assert -int 1 ` func TestAssets(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() + tests := map[uint64]string{ + 4: fmt.Sprintf(assetsTestTemplate, ""), + 5: fmt.Sprintf(assetsTestTemplate, v5extras), + } + + for v, source := range tests { + testAssetsByVersion(t, source, v) + } +} + +func testAssetsByVersion(t *testing.T, assetsTestProgram string, version uint64) { for _, field := range AssetHoldingFieldNames { - if !strings.Contains(assetsTestProgram, field) { + fs := assetHoldingFieldSpecByName[field] + if fs.version <= version && !strings.Contains(assetsTestProgram, field) { t.Errorf("TestAssets missing field %v", field) } } for _, field := range AssetParamsFieldNames { - if !strings.Contains(assetsTestProgram, field) { + fs := assetParamsFieldSpecByName[field] + if fs.version <= version && !strings.Contains(assetsTestProgram, field) { t.Errorf("TestAssets missing field %v", field) } } txn := makeSampleTxn() pre := defaultEvalParamsWithVersion(nil, &txn, directRefEnabledVersion-1) - now := defaultEvalParams(nil, &txn) + require.GreaterOrEqual(t, version, uint64(directRefEnabledVersion)) + now := defaultEvalParamsWithVersion(nil, &txn, version) ledger := makeTestLedger( map[basics.Address]uint64{ txn.Txn.Sender: 1, @@ -1358,8 +1376,12 @@ func TestAssets(t *testing.T) { // but old code cannot testProg(t, strings.Replace(assetsTestProgram, "int 0//account", "byte \"aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00\"", -1), directRefEnabledVersion-1, expect{3, "asset_holding_get AssetBalance arg 0 wanted type uint64..."}) - testApp(t, strings.Replace(assetsTestProgram, "int 0//params", "int 55", -1), pre, "invalid Asset ref") - testApp(t, strings.Replace(assetsTestProgram, "int 55", "int 0", -1), pre, "err opcode") + + if version < 5 { + // Can't run these with AppCreator anyway + testApp(t, strings.Replace(assetsTestProgram, "int 0//params", "int 55", -1), pre, "invalid Asset ref") + testApp(t, strings.Replace(assetsTestProgram, "int 55", "int 0", -1), pre, "err opcode") + } // check holdings bool value source := `intcblock 0 55 1 @@ -1380,12 +1402,12 @@ intc_2 // 1 testApp(t, source, now) // check holdings invalid offsets - ops := testProg(t, source, AssemblerMaxVersion) + ops := testProg(t, source, version) require.Equal(t, OpsByName[now.Proto.LogicSigVersion]["asset_holding_get"].Opcode, ops.Program[8]) ops.Program[9] = 0x02 _, err := EvalStateful(ops.Program, now) require.Error(t, err) - require.Contains(t, err.Error(), "invalid asset holding field 2") + require.Contains(t, err.Error(), "invalid asset_holding_get field 2") // check holdings bool value source = `intcblock 0 1 @@ -1405,12 +1427,12 @@ intc_1 ledger.newAsset(txn.Txn.Sender, 55, params) testApp(t, source, now) // check holdings invalid offsets - ops = testProg(t, source, AssemblerMaxVersion) + ops = testProg(t, source, version) require.Equal(t, OpsByName[now.Proto.LogicSigVersion]["asset_params_get"].Opcode, ops.Program[6]) ops.Program[7] = 0x20 _, err = EvalStateful(ops.Program, now) require.Error(t, err) - require.Contains(t, err.Error(), "invalid asset params field 32") + require.Contains(t, err.Error(), "invalid asset_params_get field 32") // check empty string source = `intcblock 0 1 @@ -2645,9 +2667,8 @@ func TestEnumFieldErrors(t *testing.T) { TxnFieldTypes[Amount] = origTxnType }() - ops, err := AssembleStringWithVersion(source, AssemblerMaxVersion) - require.NoError(t, err) - _, err = Eval(ops.Program, ep) + ops := testProg(t, source, AssemblerMaxVersion) + _, err := Eval(ops.Program, ep) require.Error(t, err) require.Contains(t, err.Error(), "Amount expected field type is []byte but got uint64") _, err = EvalStateful(ops.Program, ep) @@ -2655,14 +2676,16 @@ func TestEnumFieldErrors(t *testing.T) { require.Contains(t, err.Error(), "Amount expected field type is []byte but got uint64") source = `global MinTxnFee` - origGlobalType := GlobalFieldTypes[MinTxnFee] - GlobalFieldTypes[MinTxnFee] = StackBytes + + origMinTxnFs := globalFieldSpecByField[MinTxnFee] + badMinTxnFs := origMinTxnFs + badMinTxnFs.ftype = StackBytes + globalFieldSpecByField[MinTxnFee] = badMinTxnFs defer func() { - GlobalFieldTypes[MinTxnFee] = origGlobalType + globalFieldSpecByField[MinTxnFee] = origMinTxnFs }() - ops, err = AssembleStringWithVersion(source, AssemblerMaxVersion) - require.NoError(t, err) + ops = testProg(t, source, AssemblerMaxVersion) _, err = Eval(ops.Program, ep) require.Error(t, err) require.Contains(t, err.Error(), "MinTxnFee expected field type is []byte but got uint64") @@ -2698,14 +2721,15 @@ int 55 asset_holding_get AssetBalance pop ` - origAssetHoldingType := AssetHoldingFieldTypes[AssetBalance] - AssetHoldingFieldTypes[AssetBalance] = StackBytes + origBalanceFs := assetHoldingFieldSpecByField[AssetBalance] + badBalanceFs := origBalanceFs + badBalanceFs.ftype = StackBytes + assetHoldingFieldSpecByField[AssetBalance] = badBalanceFs defer func() { - AssetHoldingFieldTypes[AssetBalance] = origAssetHoldingType + assetHoldingFieldSpecByField[AssetBalance] = origBalanceFs }() - ops, err = AssembleStringWithVersion(source, AssemblerMaxVersion) - require.NoError(t, err) + ops = testProg(t, source, AssemblerMaxVersion) _, err = EvalStateful(ops.Program, ep) require.Error(t, err) require.Contains(t, err.Error(), "AssetBalance expected field type is []byte but got uint64") @@ -2714,14 +2738,15 @@ pop asset_params_get AssetTotal pop ` - origAssetTotalType := AssetParamsFieldTypes[AssetTotal] - AssetParamsFieldTypes[AssetTotal] = StackBytes + origTotalFs := assetParamsFieldSpecByField[AssetTotal] + badTotalFs := origTotalFs + badTotalFs.ftype = StackBytes + assetParamsFieldSpecByField[AssetTotal] = badTotalFs defer func() { - AssetParamsFieldTypes[AssetTotal] = origAssetTotalType + assetParamsFieldSpecByField[AssetTotal] = origTotalFs }() - ops, err = AssembleStringWithVersion(source, AssemblerMaxVersion) - require.NoError(t, err) + ops = testProg(t, source, AssemblerMaxVersion) _, err = EvalStateful(ops.Program, ep) require.Error(t, err) require.Contains(t, err.Error(), "AssetTotal expected field type is []byte but got uint64") diff --git a/data/transactions/logic/fields.go b/data/transactions/logic/fields.go index 9bc37547c..bc242859e 100644 --- a/data/transactions/logic/fields.go +++ b/data/transactions/logic/fields.go @@ -337,7 +337,7 @@ var GlobalFieldNames []string var GlobalFieldTypes []StackType type globalFieldSpec struct { - gfield GlobalField + field GlobalField ftype StackType mode runMode version uint64 @@ -384,20 +384,33 @@ const ( // AssetHoldingFieldNames are arguments to the 'asset_holding_get' opcode var AssetHoldingFieldNames []string -type assetHoldingFieldType struct { - field AssetHoldingField - ftype StackType +// AssetHoldingFieldTypes is StackUint64 StackBytes in parallel with AssetHoldingFieldNames +var AssetHoldingFieldTypes []StackType + +type assetHoldingFieldSpec struct { + field AssetHoldingField + ftype StackType + version uint64 } -var assetHoldingFieldTypeList = []assetHoldingFieldType{ - {AssetBalance, StackUint64}, - {AssetFrozen, StackUint64}, +var assetHoldingFieldSpecs = []assetHoldingFieldSpec{ + {AssetBalance, StackUint64, 2}, + {AssetFrozen, StackUint64, 2}, } -// AssetHoldingFieldTypes is StackUint64 StackBytes in parallel with AssetHoldingFieldNames -var AssetHoldingFieldTypes []StackType +var assetHoldingFieldSpecByField map[AssetHoldingField]assetHoldingFieldSpec +var assetHoldingFieldSpecByName ahfNameSpecMap -var assetHoldingFields map[string]uint64 +// simple interface used by doc generator for fields versioning +type ahfNameSpecMap map[string]assetHoldingFieldSpec + +func (s ahfNameSpecMap) getExtraFor(name string) (extra string) { + // Uses 2 here because asset fields were introduced in 2 + if s[name].version > 2 { + extra = fmt.Sprintf("LogicSigVersion >= %d.", s[name].version) + } + return +} // AssetParamsField is an enum for `asset_params_get` opcode type AssetParamsField int @@ -435,30 +448,43 @@ const ( // AssetParamsFieldNames are arguments to the 'asset_params_get' opcode var AssetParamsFieldNames []string -type assetParamsFieldType struct { - field AssetParamsField - ftype StackType +// AssetParamsFieldTypes is StackUint64 StackBytes in parallel with AssetParamsFieldNames +var AssetParamsFieldTypes []StackType + +type assetParamsFieldSpec struct { + field AssetParamsField + ftype StackType + version uint64 } -var assetParamsFieldTypeList = []assetParamsFieldType{ - {AssetTotal, StackUint64}, - {AssetDecimals, StackUint64}, - {AssetDefaultFrozen, StackUint64}, - {AssetUnitName, StackBytes}, - {AssetName, StackBytes}, - {AssetURL, StackBytes}, - {AssetMetadataHash, StackBytes}, - {AssetManager, StackBytes}, - {AssetReserve, StackBytes}, - {AssetFreeze, StackBytes}, - {AssetClawback, StackBytes}, - {AssetCreator, StackBytes}, +var assetParamsFieldSpecs = []assetParamsFieldSpec{ + {AssetTotal, StackUint64, 2}, + {AssetDecimals, StackUint64, 2}, + {AssetDefaultFrozen, StackUint64, 2}, + {AssetUnitName, StackBytes, 2}, + {AssetName, StackBytes, 2}, + {AssetURL, StackBytes, 2}, + {AssetMetadataHash, StackBytes, 2}, + {AssetManager, StackBytes, 2}, + {AssetReserve, StackBytes, 2}, + {AssetFreeze, StackBytes, 2}, + {AssetClawback, StackBytes, 2}, + {AssetCreator, StackBytes, 5}, } -// AssetParamsFieldTypes is StackUint64 StackBytes in parallel with AssetParamsFieldNames -var AssetParamsFieldTypes []StackType +var assetParamsFieldSpecByField map[AssetParamsField]assetParamsFieldSpec +var assetParamsFieldSpecByName apfNameSpecMap -var assetParamsFields map[string]uint64 +// simple interface used by doc generator for fields versioning +type apfNameSpecMap map[string]assetParamsFieldSpec + +func (s apfNameSpecMap) getExtraFor(name string) (extra string) { + // Uses 2 here because asset fields were introduced in 2 + if s[name].version > 2 { + extra = fmt.Sprintf("LogicSigVersion >= %d.", s[name].version) + } + return +} // AppParamsField is an enum for `app_params_get` opcode type AppParamsField int @@ -488,26 +514,39 @@ const ( // AppParamsFieldNames are arguments to the 'app_params_get' opcode var AppParamsFieldNames []string -type appParamsFieldType struct { - field AppParamsField - ftype StackType +// AppParamsFieldTypes is StackUint64 StackBytes in parallel with AppParamsFieldNames +var AppParamsFieldTypes []StackType + +type appParamsFieldSpec struct { + field AppParamsField + ftype StackType + version uint64 } -var appParamsFieldTypeList = []appParamsFieldType{ - {AppApprovalProgram, StackBytes}, - {AppClearStateProgram, StackBytes}, - {AppGlobalNumUint, StackUint64}, - {AppGlobalNumByteSlice, StackUint64}, - {AppLocalNumUint, StackUint64}, - {AppLocalNumByteSlice, StackUint64}, - {AppExtraProgramPages, StackUint64}, - {AppCreator, StackBytes}, +var appParamsFieldSpecs = []appParamsFieldSpec{ + {AppApprovalProgram, StackBytes, 5}, + {AppClearStateProgram, StackBytes, 5}, + {AppGlobalNumUint, StackUint64, 5}, + {AppGlobalNumByteSlice, StackUint64, 5}, + {AppLocalNumUint, StackUint64, 5}, + {AppLocalNumByteSlice, StackUint64, 5}, + {AppExtraProgramPages, StackUint64, 5}, + {AppCreator, StackBytes, 5}, } -// AppParamsFieldTypes is StackUint64 StackBytes in parallel with AppParamsFieldNames -var AppParamsFieldTypes []StackType +var appParamsFieldSpecByField map[AppParamsField]appParamsFieldSpec +var appParamsFieldSpecByName appNameSpecMap + +// simple interface used by doc generator for fields versioning +type appNameSpecMap map[string]appParamsFieldSpec -var appParamsFields map[string]uint64 +func (s appNameSpecMap) getExtraFor(name string) (extra string) { + // Uses 2 here because app fields were introduced in 5 + if s[name].version > 5 { + extra = fmt.Sprintf("LogicSigVersion >= %d.", s[name].version) + } + return +} func init() { TxnFieldNames = make([]string, int(invalidTxnField)) @@ -535,8 +574,8 @@ func init() { GlobalFieldTypes = make([]StackType, len(GlobalFieldNames)) globalFieldSpecByField = make(map[GlobalField]globalFieldSpec, len(GlobalFieldNames)) for _, s := range globalFieldSpecs { - GlobalFieldTypes[int(s.gfield)] = s.ftype - globalFieldSpecByField[s.gfield] = s + GlobalFieldTypes[int(s.field)] = s.ftype + globalFieldSpecByField[s.field] = s } globalFieldSpecByName = make(gfNameSpecMap, len(GlobalFieldNames)) for i, gfn := range GlobalFieldNames { @@ -548,12 +587,14 @@ func init() { AssetHoldingFieldNames[int(i)] = i.String() } AssetHoldingFieldTypes = make([]StackType, len(AssetHoldingFieldNames)) - for _, ft := range assetHoldingFieldTypeList { - AssetHoldingFieldTypes[int(ft.field)] = ft.ftype + assetHoldingFieldSpecByField = make(map[AssetHoldingField]assetHoldingFieldSpec, len(AssetHoldingFieldNames)) + for _, s := range assetHoldingFieldSpecs { + AssetHoldingFieldTypes[int(s.field)] = s.ftype + assetHoldingFieldSpecByField[s.field] = s } - assetHoldingFields = make(map[string]uint64) - for i, fn := range AssetHoldingFieldNames { - assetHoldingFields[fn] = uint64(i) + assetHoldingFieldSpecByName = make(ahfNameSpecMap, len(AssetHoldingFieldNames)) + for i, ahfn := range AssetHoldingFieldNames { + assetHoldingFieldSpecByName[ahfn] = assetHoldingFieldSpecByField[AssetHoldingField(i)] } AssetParamsFieldNames = make([]string, int(invalidAssetParamsField)) @@ -561,12 +602,14 @@ func init() { AssetParamsFieldNames[int(i)] = i.String() } AssetParamsFieldTypes = make([]StackType, len(AssetParamsFieldNames)) - for _, ft := range assetParamsFieldTypeList { - AssetParamsFieldTypes[int(ft.field)] = ft.ftype + assetParamsFieldSpecByField = make(map[AssetParamsField]assetParamsFieldSpec, len(AssetParamsFieldNames)) + for _, s := range assetParamsFieldSpecs { + AssetParamsFieldTypes[int(s.field)] = s.ftype + assetParamsFieldSpecByField[s.field] = s } - assetParamsFields = make(map[string]uint64) - for i, fn := range AssetParamsFieldNames { - assetParamsFields[fn] = uint64(i) + assetParamsFieldSpecByName = make(apfNameSpecMap, len(AssetParamsFieldNames)) + for i, apfn := range AssetParamsFieldNames { + assetParamsFieldSpecByName[apfn] = assetParamsFieldSpecByField[AssetParamsField(i)] } AppParamsFieldNames = make([]string, int(invalidAppParamsField)) @@ -574,12 +617,14 @@ func init() { AppParamsFieldNames[int(i)] = i.String() } AppParamsFieldTypes = make([]StackType, len(AppParamsFieldNames)) - for _, ft := range appParamsFieldTypeList { - AppParamsFieldTypes[int(ft.field)] = ft.ftype + appParamsFieldSpecByField = make(map[AppParamsField]appParamsFieldSpec, len(AppParamsFieldNames)) + for _, s := range appParamsFieldSpecs { + AppParamsFieldTypes[int(s.field)] = s.ftype + appParamsFieldSpecByField[s.field] = s } - appParamsFields = make(map[string]uint64) - for i, fn := range AppParamsFieldNames { - appParamsFields[fn] = uint64(i) + appParamsFieldSpecByName = make(appNameSpecMap, len(AppParamsFieldNames)) + for i, apfn := range AppParamsFieldNames { + appParamsFieldSpecByName[apfn] = appParamsFieldSpecByField[AppParamsField(i)] } txnTypeIndexes = make(map[string]uint64, len(TxnTypeNames)) diff --git a/data/transactions/logic/fields_test.go b/data/transactions/logic/fields_test.go index b36e715e3..e88f3820f 100644 --- a/data/transactions/logic/fields_test.go +++ b/data/transactions/logic/fields_test.go @@ -47,7 +47,7 @@ func TestGlobalFieldsVersions(t *testing.T) { ledger := makeTestLedger(nil) for _, field := range fields { - text := fmt.Sprintf("global %s", field.gfield.String()) + text := fmt.Sprintf("global %s", field.field.String()) // check assembler fails if version before introduction testLine(t, text, assemblerNoVersion, "...available in version...") for v := uint64(0); v < field.version; v++ { @@ -76,13 +76,13 @@ func TestGlobalFieldsVersions(t *testing.T) { ops.Program[0] = byte(preLogicVersion) // set version _, err = Eval(ops.Program, ep) require.Error(t, err) - require.Contains(t, err.Error(), "invalid global[") + require.Contains(t, err.Error(), "invalid global field") // check opcodes failures on 0 version ops.Program[0] = 0 // set version to 0 _, err = Eval(ops.Program, ep) require.Error(t, err) - require.Contains(t, err.Error(), "invalid global[") + require.Contains(t, err.Error(), "invalid global field") } } @@ -178,3 +178,63 @@ func TestTxnFieldVersions(t *testing.T) { } } } + +func TestAssetParamsFieldsVersions(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + var fields []assetParamsFieldSpec + for _, fs := range assetParamsFieldSpecs { + if fs.version > 2 { + fields = append(fields, fs) + } + } + require.Greater(t, len(fields), 0) + + for _, field := range fields { + // Need to use intc so we can "backversion" the + // program and not have it fail because of pushint. + text := fmt.Sprintf("intcblock 0 1; intc_0; asset_params_get %s; pop; pop; intc_1", field.field.String()) + // check assembler fails if version before introduction + for v := uint64(2); v <= AssemblerMaxVersion; v++ { + ep, _ := makeSampleEnv() + ep.Proto.LogicSigVersion = v + if field.version > v { + testProg(t, text, v, expect{3, "...available in version..."}) + ops := testProg(t, text, field.version) // assemble in the future + scratch := ops.Program + scratch[0] = byte(v) // but we'll tweak the version byte back to v + err := CheckStateful(scratch, ep) + require.NoError(t, err) + pass, err := EvalStateful(scratch, ep) // so eval fails on future field + require.False(t, pass) + require.Error(t, err) + require.Contains(t, err.Error(), "invalid asset_params_get field") + } else { + testProg(t, text, v) + testApp(t, text, ep) + } + } + + } +} + +func TestFieldVersions(t *testing.T) { + // This test is weird, it confirms that we don't need to + // bother with a "good" test for AssetHolding and AppParams + // fields. It will fail if we add a field that has a + // different teal debut version, and then we'll need a test + // like TestAssetParamsFieldsVersions that checks the field is + // unavailable before its debut. + + partitiontest.PartitionTest(t) + t.Parallel() + + for _, fs := range assetHoldingFieldSpecs { + require.Equal(t, uint64(2), fs.version) + } + + for _, fs := range appParamsFieldSpecs { + require.Equal(t, uint64(5), fs.version) + } +} |