diff options
author | John Jannotti <john.jannotti@algorand.com> | 2022-02-01 16:51:48 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-02-01 16:51:48 -0500 |
commit | 7c2329ce0a3405a62a841369c2aed3a0a81349e7 (patch) | |
tree | 49266ace2bd67d2e840ccee3f184fbca70f1c6c5 | |
parent | 84b52e7567eec1641768fc9441adbfed3cc3a875 (diff) |
Tsachi cr (#3538)
-rw-r--r-- | cmd/opdoc/opdoc.go | 48 | ||||
-rw-r--r-- | cmd/tealdbg/cdtState.go | 3 | ||||
-rw-r--r-- | data/transactions/blackbox_test.go | 82 | ||||
-rw-r--r-- | data/transactions/logic/assembler.go | 9 | ||||
-rw-r--r-- | data/transactions/logic/backwardCompat_test.go | 1 | ||||
-rw-r--r-- | data/transactions/logic/blackbox_test.go | 2 | ||||
-rw-r--r-- | data/transactions/logic/eval.go | 147 | ||||
-rw-r--r-- | data/transactions/logic/evalStateful_test.go | 6 | ||||
-rw-r--r-- | data/transactions/logic/eval_test.go | 25 | ||||
-rw-r--r-- | data/transactions/logic/export_test.go | 4 | ||||
-rw-r--r-- | data/transactions/logic/fields_test.go | 4 | ||||
-rw-r--r-- | data/transactions/logic/ledger_test.go | 74 | ||||
-rw-r--r-- | ledger/internal/applications.go | 18 | ||||
-rw-r--r-- | ledger/internal/applications_test.go | 4 |
14 files changed, 248 insertions, 179 deletions
diff --git a/cmd/opdoc/opdoc.go b/cmd/opdoc/opdoc.go index 7167583d3..9b6fff979 100644 --- a/cmd/opdoc/opdoc.go +++ b/cmd/opdoc/opdoc.go @@ -235,22 +235,25 @@ func opToMarkdown(out io.Writer, op *logic.OpSpec) (err error) { if !op.Modes.Any() { fmt.Fprintf(out, "- Mode: %s\n", op.Modes.String()) } - if op.Name == "global" { + switch op.Name { + case "global": globalFieldsMarkdown(out) - } else if op.Name == "txn" { + case "txn": transactionFieldsMarkdown(out) fmt.Fprintf(out, "\nTypeEnum mapping:\n\n") typeEnumTableMarkdown(out) - } else if op.Name == "asset_holding_get" { + case "asset_holding_get": assetHoldingFieldsMarkdown(out) - } else if op.Name == "asset_params_get" { + case "asset_params_get": assetParamsFieldsMarkdown(out) - } else if op.Name == "app_params_get" { + case "app_params_get": appParamsFieldsMarkdown(out) - } else if op.Name == "acct_params_get" { + case "acct_params_get": acctParamsFieldsMarkdown(out) - } else if strings.HasPrefix(op.Name, "ecdsa") { - ecDsaCurvesMarkdown(out) + default: + if strings.HasPrefix(op.Name, "ecdsa") { + ecDsaCurvesMarkdown(out) + } } ode := logic.OpDocExtra(op.Name) if ode != "" { @@ -369,42 +372,51 @@ func buildLanguageSpec(opGroups map[string][]string) *LanguageSpec { } } +func create(file string) *os.File { + f, err := os.Create(file) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to create '%s': %v", file, err) + os.Exit(1) + } + return f +} + func main() { - opcodesMd, _ := os.Create("TEAL_opcodes.md") + opcodesMd := create("TEAL_opcodes.md") opsToMarkdown(opcodesMd) opcodesMd.Close() opGroups := make(map[string][]string, len(logic.OpSpecs)) for grp, names := range logic.OpGroups { fname := fmt.Sprintf("%s.md", grp) fname = strings.ReplaceAll(fname, " ", "_") - fout, _ := os.Create(fname) + fout := create(fname) opGroupMarkdownTable(names, fout) fout.Close() for _, opname := range names { opGroups[opname] = append(opGroups[opname], grp) } } - constants, _ := os.Create("named_integer_constants.md") + constants := create("named_integer_constants.md") integerConstantsTableMarkdown(constants) constants.Close() - txnfields, _ := os.Create("txn_fields.md") + txnfields := create("txn_fields.md") fieldSpecsMarkdown(txnfields, logic.TxnFieldNames, logic.TxnFieldSpecByName) txnfields.Close() - globalfields, _ := os.Create("global_fields.md") + globalfields := create("global_fields.md") fieldSpecsMarkdown(globalfields, logic.GlobalFieldNames, logic.GlobalFieldSpecByName) globalfields.Close() - assetholding, _ := os.Create("asset_holding_fields.md") + assetholding := create("asset_holding_fields.md") fieldSpecsMarkdown(assetholding, logic.AssetHoldingFieldNames, logic.AssetHoldingFieldSpecByName) assetholding.Close() - assetparams, _ := os.Create("asset_params_fields.md") + assetparams := create("asset_params_fields.md") fieldSpecsMarkdown(assetparams, logic.AssetParamsFieldNames, logic.AssetParamsFieldSpecByName) assetparams.Close() - appparams, _ := os.Create("app_params_fields.md") + appparams := create("app_params_fields.md") fieldSpecsMarkdown(appparams, logic.AppParamsFieldNames, logic.AppParamsFieldSpecByName) appparams.Close() @@ -412,12 +424,12 @@ func main() { fieldSpecsMarkdown(acctparams, logic.AcctParamsFieldNames, logic.AcctParamsFieldSpecByName) acctparams.Close() - langspecjs, _ := os.Create("langspec.json") + langspecjs := create("langspec.json") enc := json.NewEncoder(langspecjs) enc.Encode(buildLanguageSpec(opGroups)) langspecjs.Close() - tealtm, _ := os.Create("teal.tmLanguage.json") + tealtm := create("teal.tmLanguage.json") enc = json.NewEncoder(tealtm) enc.SetIndent("", " ") enc.Encode(buildSyntaxHighlight()) diff --git a/cmd/tealdbg/cdtState.go b/cmd/tealdbg/cdtState.go index 5e7ee9825..58ea184f0 100644 --- a/cmd/tealdbg/cdtState.go +++ b/cmd/tealdbg/cdtState.go @@ -368,7 +368,8 @@ func prepareTxn(txn *transactions.Transaction, groupIndex int) []fieldDesc { field == int(logic.CreatedApplicationID) || field == int(logic.CreatedAssetID) || field == int(logic.Logs) || - field == int(logic.NumLogs) { + field == int(logic.NumLogs) || + field == int(logic.LastLog) { continue } var value string diff --git a/data/transactions/blackbox_test.go b/data/transactions/blackbox_test.go new file mode 100644 index 000000000..b8f35e9a8 --- /dev/null +++ b/data/transactions/blackbox_test.go @@ -0,0 +1,82 @@ +// Copyright (C) 2019-2022 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see <https://www.gnu.org/licenses/>. + +package transactions_test + +import ( + "testing" + + "github.com/algorand/go-algorand/data/transactions" + "github.com/algorand/go-algorand/data/txntest" + "github.com/algorand/go-algorand/protocol" + "github.com/algorand/go-algorand/test/partitiontest" + "github.com/stretchr/testify/require" +) + +func TestFeeCredit(t *testing.T) { + partitiontest.PartitionTest(t) + + c, err := transactions.FeeCredit([]transactions.SignedTxnWithAD{ + txntest.Txn{Fee: 5}.SignedTxnWithAD(), + }, 5) + require.NoError(t, err) + require.Equal(t, c, uint64(0)) + + c, err = transactions.FeeCredit([]transactions.SignedTxnWithAD{ + txntest.Txn{Fee: 4}.SignedTxnWithAD(), + }, 5) + require.Error(t, err) + + c, err = transactions.FeeCredit([]transactions.SignedTxnWithAD{ + txntest.Txn{Fee: 5}.SignedTxnWithAD(), + }, 4) + require.NoError(t, err) + require.Equal(t, c, uint64(1)) + + c, err = transactions.FeeCredit([]transactions.SignedTxnWithAD{ + txntest.Txn{Fee: 5}.SignedTxnWithAD(), + txntest.Txn{Fee: 5}.SignedTxnWithAD(), + }, 5) + require.NoError(t, err) + require.Equal(t, c, uint64(0)) + + c, err = transactions.FeeCredit([]transactions.SignedTxnWithAD{ + txntest.Txn{Fee: 5}.SignedTxnWithAD(), + txntest.Txn{Type: protocol.CompactCertTx, Fee: 0}.SignedTxnWithAD(), + }, 5) + require.NoError(t, err) + require.Equal(t, c, uint64(0)) + + c, err = transactions.FeeCredit([]transactions.SignedTxnWithAD{ + txntest.Txn{Fee: 5}.SignedTxnWithAD(), + txntest.Txn{Fee: 5}.SignedTxnWithAD(), + txntest.Txn{Fee: 5}.SignedTxnWithAD(), + }, 5) + require.NoError(t, err) + require.Equal(t, c, uint64(0)) + + c, err = transactions.FeeCredit([]transactions.SignedTxnWithAD{}, 5) + require.NoError(t, err) + require.Equal(t, c, uint64(0)) + + c, err = transactions.FeeCredit([]transactions.SignedTxnWithAD{ + txntest.Txn{Fee: 5}.SignedTxnWithAD(), + txntest.Txn{Fee: 25}.SignedTxnWithAD(), + txntest.Txn{Fee: 5}.SignedTxnWithAD(), + }, 5) + require.NoError(t, err) + require.Equal(t, c, uint64(20)) +} diff --git a/data/transactions/logic/assembler.go b/data/transactions/logic/assembler.go index 5d5af3dec..8202af0bc 100644 --- a/data/transactions/logic/assembler.go +++ b/data/transactions/logic/assembler.go @@ -823,14 +823,15 @@ func asmTxn(ops *OpStream, spec *OpSpec, args []string) error { // asmTxn2 delegates to asmTxn or asmTxna depending on number of operands func asmTxn2(ops *OpStream, spec *OpSpec, args []string) error { - if len(args) == 1 { + switch len(args) { + case 1: return asmTxn(ops, spec, args) - } - if len(args) == 2 { + case 2: txna := OpsByName[ops.Version]["txna"] return asmTxna(ops, &txna, args) + default: + return ops.error("txn expects one or two arguments") } - return ops.error("txn expects one or two arguments") } // asmTxna also assemble asmItxna diff --git a/data/transactions/logic/backwardCompat_test.go b/data/transactions/logic/backwardCompat_test.go index 21985d749..b5917219d 100644 --- a/data/transactions/logic/backwardCompat_test.go +++ b/data/transactions/logic/backwardCompat_test.go @@ -308,7 +308,6 @@ func TestBackwardCompatTEALv1(t *testing.T) { // Costs for v2 should be higher because of hash opcode cost changes ep2, tx, _ := makeSampleEnvWithVersion(2) ep2.Proto.LogicSigMaxCost = 2307 - // ep2.TxnGroup[0].Lsig.Logic = opsV2.Program ep2.TxnGroup[0].Lsig.Args = [][]byte{data[:], sig[:], pk[:], tx.Sender[:], tx.Note} // Eval doesn't fail, but it would be ok (better?) if it did testLogicBytes(t, opsV2.Program, ep2, "static cost", "") diff --git a/data/transactions/logic/blackbox_test.go b/data/transactions/logic/blackbox_test.go index d9a01f02e..e26f84a8f 100644 --- a/data/transactions/logic/blackbox_test.go +++ b/data/transactions/logic/blackbox_test.go @@ -84,7 +84,7 @@ func TestNewAppEvalParams(t *testing.T) { require.NotNil(t, ep) require.Equal(t, ep.TxnGroup, testCase.group) require.Equal(t, *ep.Proto, param) - if reflect.DeepEqual(param, config.Consensus[protocol.ConsensusV29]) { + if reflect.DeepEqual(param, config.Consensus[protocol.ConsensusV29]) || testCase.numAppCalls == 0 { require.Nil(t, ep.PooledApplicationBudget) } else if reflect.DeepEqual(param, config.Consensus[protocol.ConsensusFuture]) { require.Equal(t, *ep.PooledApplicationBudget, param.MaxAppProgramCost*testCase.numAppCalls) diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index d06d3f1d1..eb4d18f11 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -207,8 +207,7 @@ func (sv *stackValue) toTealValue() (tv basics.TealValue) { // LedgerForLogic represents ledger API for Stateful TEAL program type LedgerForLogic interface { - Balance(addr basics.Address) (basics.MicroAlgos, error) - MinBalance(addr basics.Address, proto *config.ConsensusParams) (basics.MicroAlgos, error) + AccountData(addr basics.Address) (basics.AccountData, error) Authorizer(addr basics.Address) (basics.Address, error) Round() basics.Round LatestTimestamp() int64 @@ -314,12 +313,12 @@ func NewEvalParams(txgroup []transactions.SignedTxnWithAD, proto *config.Consens credit, _ := transactions.FeeCredit(txgroup, proto.MinTxnFee) - if proto.EnableAppCostPooling { + if proto.EnableAppCostPooling && apps > 0 { pooledApplicationBudget = new(int) *pooledApplicationBudget = apps * proto.MaxAppProgramCost } - if proto.EnableInnerTransactionPooling { + if proto.EnableInnerTransactionPooling && apps > 0 { pooledAllowedInners = new(int) *pooledAllowedInners = proto.MaxTxGroupSize * proto.MaxInnerTransactions } @@ -495,13 +494,13 @@ const ( StackNone StackType = iota // StackAny in an OpSpec shows that the op pops or yield any type - StackAny StackType = iota + StackAny // StackUint64 in an OpSpec shows that the op pops or yields a uint64 - StackUint64 StackType = iota + StackUint64 // StackBytes in an OpSpec shows that the op pops or yields a []byte - StackBytes StackType = iota + StackBytes ) // StackTypes is an alias for a list of StackType with syntactic sugar @@ -552,6 +551,9 @@ func EvalContract(program []byte, gi int, aid basics.AppIndex, params *EvalParam if params.Ledger == nil { return false, nil, errors.New("no ledger in contract eval") } + if aid == 0 { + return false, nil, errors.New("0 appId in contract eval") + } cx := EvalContext{ EvalParams: params, runModeFlags: runModeApplication, @@ -2023,12 +2025,10 @@ func TxnFieldToTealValue(txn *transactions.Transaction, groupIndex int, field Tx if groupIndex < 0 { return basics.TealValue{}, fmt.Errorf("negative groupIndex %d", groupIndex) } - cx := EvalContext{ - GroupIndex: groupIndex, - Txn: &transactions.SignedTxnWithAD{SignedTxn: transactions.SignedTxn{Txn: *txn}}, - } + var cx EvalContext + stxnad := &transactions.SignedTxnWithAD{SignedTxn: transactions.SignedTxn{Txn: *txn}} fs := txnFieldSpecByField[field] - sv, err := cx.txnFieldToStack(cx.Txn, &fs, arrayFieldIdx, groupIndex, false) + sv, err := cx.txnFieldToStack(stxnad, &fs, arrayFieldIdx, groupIndex, false) return sv.toTealValue(), err } @@ -2272,9 +2272,9 @@ func (cx *EvalContext) fetchField(field TxnField, expectArray bool) (*txnFieldSp type txnSource int const ( - srcGroup txnSource = iota - srcInner = iota - srcInnerGroup = iota + srcGroup txnSource = iota + srcInner + srcInnerGroup ) // opTxnImpl implements all of the txn variants. Each form of txn opcode should @@ -2566,35 +2566,38 @@ func opGitxnas(cx *EvalContext) { cx.stack[last] = sv } -func opGaidImpl(cx *EvalContext, gi int, opName string) (sv stackValue, err error) { - if gi >= len(cx.TxnGroup) { - err = fmt.Errorf("%s lookup TxnGroup[%d] but it only has %d", opName, gi, len(cx.TxnGroup)) +func opGaidImpl(cx *EvalContext, giw uint64, opName string) (sv stackValue, err error) { + if giw >= uint64(len(cx.TxnGroup)) { + err = fmt.Errorf("%s lookup TxnGroup[%d] but it only has %d", opName, giw, len(cx.TxnGroup)) return - } else if gi > cx.GroupIndex { + } + // Is now assured smalled than a len() so fits in int. + gi := int(giw) + if gi > cx.GroupIndex { err = fmt.Errorf("%s can't get creatable ID of txn ahead of the current one (index %d) in the transaction group", opName, gi) return - } else if gi == cx.GroupIndex { + } + if gi == cx.GroupIndex { err = fmt.Errorf("%s is only for accessing creatable IDs of previous txns, use `global CurrentApplicationID` instead to access the current app's creatable ID", opName) return - } else if txn := cx.TxnGroup[gi].Txn; !(txn.Type == protocol.ApplicationCallTx || txn.Type == protocol.AssetConfigTx) { + } + if txn := cx.TxnGroup[gi].Txn; !(txn.Type == protocol.ApplicationCallTx || txn.Type == protocol.AssetConfigTx) { err = fmt.Errorf("can't use %s on txn that is not an app call nor an asset config txn with index %d", opName, gi) return } - cid, err := cx.getCreatableID(gi) - if cid == 0 { - err = fmt.Errorf("%s can't read creatable ID from txn with group index %d because the txn did not create anything", opName, gi) - return + if aid := cx.TxnGroup[gi].ApplyData.ConfigAsset; aid != 0 { + return stackValue{Uint: uint64(aid)}, nil } - - sv = stackValue{ - Uint: cid, + if aid := cx.TxnGroup[gi].ApplyData.ApplicationID; aid != 0 { + return stackValue{Uint: uint64(aid)}, nil } + err = fmt.Errorf("%s: index %d did not create anything", opName, gi) return } func opGaid(cx *EvalContext) { - gi := int(cx.program[cx.pc+1]) + gi := uint64(cx.program[cx.pc+1]) sv, err := opGaidImpl(cx, gi, "gaid") if err != nil { cx.err = err @@ -2606,14 +2609,9 @@ func opGaid(cx *EvalContext) { func opGaids(cx *EvalContext) { last := len(cx.stack) - 1 - gi := cx.stack[last].Uint - if gi >= uint64(len(cx.TxnGroup)) { - cx.err = fmt.Errorf("gaids lookup TxnGroup[%d] but it only has %d", gi, len(cx.TxnGroup)) - return - } - sv, err := opGaidImpl(cx, int(gi), "gaids") + sv, err := opGaidImpl(cx, gi, "gaids") if err != nil { cx.err = err return @@ -2657,16 +2655,6 @@ func (cx *EvalContext) getApplicationAddress(app basics.AppIndex) basics.Address return appAddr } -func (cx *EvalContext) getCreatableID(groupIndex int) (cid uint64, err error) { - if aid := cx.TxnGroup[groupIndex].ApplyData.ConfigAsset; aid != 0 { - return uint64(aid), nil - } - if aid := cx.TxnGroup[groupIndex].ApplyData.ApplicationID; aid != 0 { - return uint64(aid), nil - } - return 0, fmt.Errorf("Index %d did not create anything", groupIndex) -} - func (cx *EvalContext) getCreatorAddress() ([]byte, error) { if cx.Ledger == nil { return nil, fmt.Errorf("ledger not available") @@ -3044,7 +3032,7 @@ func opGloadss(cx *EvalContext) { gi := cx.stack[prev].Uint if gi >= uint64(len(cx.TxnGroup)) { - cx.err = fmt.Errorf("gloads lookup TxnGroup[%d] but it only has %d", gi, len(cx.TxnGroup)) + cx.err = fmt.Errorf("gloadss lookup TxnGroup[%d] but it only has %d", gi, len(cx.TxnGroup)) return } scratchIdx := cx.stack[last].Uint @@ -3363,49 +3351,50 @@ func (cx *EvalContext) mutableAccountReference(account stackValue) (basics.Addre return addr, accountIdx, err } -type opQuery func(basics.Address, *config.ConsensusParams) (basics.MicroAlgos, error) - -func opBalanceQuery(cx *EvalContext, query opQuery, item string) error { +func opBalance(cx *EvalContext) { + if cx.Ledger == nil { + cx.err = fmt.Errorf("ledger not available") + return + } last := len(cx.stack) - 1 // account (index or actual address) addr, _, err := cx.accountReference(cx.stack[last]) if err != nil { - return err + cx.err = err + return } - microAlgos, err := query(addr, cx.Proto) + account, err := cx.Ledger.AccountData(addr) if err != nil { - return fmt.Errorf("failed to fetch %s of %v: %w", item, addr, err) + cx.err = err + return } cx.stack[last].Bytes = nil - cx.stack[last].Uint = microAlgos.Raw - return nil + cx.stack[last].Uint = account.MicroAlgos.Raw } -func opBalance(cx *EvalContext) { + +func opMinBalance(cx *EvalContext) { if cx.Ledger == nil { cx.err = fmt.Errorf("ledger not available") return } + last := len(cx.stack) - 1 // account (index or actual address) - balanceQuery := func(addr basics.Address, _ *config.ConsensusParams) (basics.MicroAlgos, error) { - return cx.Ledger.Balance(addr) - } - err := opBalanceQuery(cx, balanceQuery, "balance") + addr, _, err := cx.accountReference(cx.stack[last]) if err != nil { cx.err = err - } -} -func opMinBalance(cx *EvalContext) { - if cx.Ledger == nil { - cx.err = fmt.Errorf("ledger not available") return } - err := opBalanceQuery(cx, cx.Ledger.MinBalance, "minimum balance") + account, err := cx.Ledger.AccountData(addr) if err != nil { cx.err = err + return } + + cx.stack[last].Bytes = nil + cx.stack[last].Uint = account.MinBalance(cx.Proto).Raw } func opAppOptedIn(cx *EvalContext) { @@ -3929,36 +3918,23 @@ func opAcctParamsGet(cx *EvalContext) { return } - bal, err := cx.Ledger.Balance(addr) + account, err := cx.Ledger.AccountData(addr) if err != nil { cx.err = err return } - exist := boolToUint(bal.Raw > 0) + + exist := boolToUint(account.MicroAlgos.Raw > 0) var value stackValue switch fs.field { case AcctBalance: - value.Uint = bal.Raw + value.Uint = account.MicroAlgos.Raw case AcctMinBalance: - mbal, err := cx.Ledger.MinBalance(addr, cx.Proto) - if err != nil { - cx.err = err - return - } - value.Uint = mbal.Raw + value.Uint = account.MinBalance(cx.Proto).Raw case AcctAuthAddr: - auth, err := cx.Ledger.Authorizer(addr) - if err != nil { - cx.err = err - return - } - if auth == addr { - value.Bytes = zeroAddress[:] - } else { - value.Bytes = auth[:] - } + value.Bytes = account.AuthAddr[:] } cx.stack[last] = value cx.stack = append(cx.stack, stackValue{Uint: exist}) @@ -3967,7 +3943,7 @@ func opAcctParamsGet(cx *EvalContext) { func opLog(cx *EvalContext) { last := len(cx.stack) - 1 - if len(cx.Txn.EvalDelta.Logs) == MaxLogCalls { + if len(cx.Txn.EvalDelta.Logs) >= MaxLogCalls { cx.err = fmt.Errorf("too many log calls in program. up to %d is allowed", MaxLogCalls) return } @@ -4546,5 +4522,6 @@ func opBase64Decode(cx *EvalContext) { if encodingField == StdEncoding { encoding = base64.StdEncoding } + encoding = encoding.Strict() cx.stack[last].Bytes, cx.err = base64Decode(cx.stack[last].Bytes, encoding) } diff --git a/data/transactions/logic/evalStateful_test.go b/data/transactions/logic/evalStateful_test.go index 9bacb5ae3..804e2ec77 100644 --- a/data/transactions/logic/evalStateful_test.go +++ b/data/transactions/logic/evalStateful_test.go @@ -462,7 +462,7 @@ func TestMinBalance(t *testing.T) { ledger.NewLocals(tx.Sender, 77) // create + optin + 10 schema base + 4 ints + 6 bytes (local // and global count b/c NewLocals opts the creator in) - minb := 2*1002 + 10*1003 + 4*1004 + 6*1005 + minb := 1002 + 1006 + 10*1003 + 4*1004 + 6*1005 testApp(t, fmt.Sprintf("int 0; min_balance; int %d; ==", 2002+minb), ep) // request extra program pages, min balance increase withepp := makeApp(1, 2, 3, 4) @@ -1010,7 +1010,7 @@ intc_2 // 1 ops := testProg(t, source, version) require.Equal(t, OpsByName[now.Proto.LogicSigVersion]["asset_holding_get"].Opcode, ops.Program[8]) ops.Program[9] = 0x02 - _, err := EvalApp(ops.Program, 0, 0, now) + _, err := EvalApp(ops.Program, 0, 888, now) require.Error(t, err) require.Contains(t, err.Error(), "invalid asset_holding_get field 2") @@ -1035,7 +1035,7 @@ intc_1 ops = testProg(t, source, version) require.Equal(t, OpsByName[now.Proto.LogicSigVersion]["asset_params_get"].Opcode, ops.Program[6]) ops.Program[7] = 0x20 - _, err = EvalApp(ops.Program, 0, 0, now) + _, err = EvalApp(ops.Program, 0, 888, now) require.Error(t, err) require.Contains(t, err.Error(), "invalid asset_params_get field 32") diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index 5921e7511..ebf7ed9ba 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -62,6 +62,7 @@ func makeTestProtoV(version uint64) *config.ConsensusParams { SchemaMinBalancePerEntry: 1003, SchemaUintMinBalance: 1004, SchemaBytesMinBalance: 1005, + AppFlatOptInMinBalance: 1006, MaxInnerTransactions: 4, MaxTxGroupSize: 8, @@ -1649,12 +1650,12 @@ func TestGaid(t *testing.T) { ep.Ledger = MakeLedger(nil) // should fail when no creatable was created - _, err := EvalApp(check0.Program, 1, 0, ep) + _, err := EvalApp(check0.Program, 1, 888, ep) require.Error(t, err) - require.Contains(t, err.Error(), "the txn did not create anything") + require.Contains(t, err.Error(), "did not create anything") ep.TxnGroup[0].ApplyData.ConfigAsset = 100 - pass, err := EvalApp(check0.Program, 1, 0, ep) + pass, err := EvalApp(check0.Program, 1, 888, ep) if !pass || err != nil { t.Log(ep.Trace.String()) } @@ -1663,18 +1664,18 @@ func TestGaid(t *testing.T) { // should fail when accessing future transaction in group check2 := testProg(t, "gaid 2; int 0; >", 4) - _, err = EvalApp(check2.Program, 1, 0, ep) + _, err = EvalApp(check2.Program, 1, 888, 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 - _, err = EvalApp(check0.Program, 0, 0, ep) + _, err = EvalApp(check0.Program, 0, 888, ep) require.Error(t, err) require.Contains(t, err.Error(), "gaid is only for accessing creatable IDs of previous txns") // should fail on non-creatable ep.TxnGroup[0].Txn.Type = protocol.PaymentTx - _, err = EvalApp(check0.Program, 1, 0, ep) + _, err = EvalApp(check0.Program, 1, 888, 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 @@ -4543,9 +4544,10 @@ func TestPcDetails(t *testing.T) { ops := testProg(t, test.source, LogicVersion) ep, _, _ := makeSampleEnv() - pass, cx, err := EvalContract(ops.Program, 0, 0, ep) + pass, cx, err := EvalContract(ops.Program, 0, 888, ep) require.Error(t, err) require.False(t, pass) + require.NotNil(t, cx) // cx comes back nil if we couldn't even run assert.Equal(t, test.pc, cx.pc, ep.Trace.String()) @@ -4646,6 +4648,15 @@ By Herman Melville`, "", {"SQ=", "URLEncoding", "", "byte 3"}, {"SQ===", "StdEncoding", "", "byte 4"}, {"SQ===", "URLEncoding", "", "byte 4"}, + + // Strict decoding. "Y" is normally encoded as "WQ==", as confirmed by the first test + {"WQ==", "StdEncoding", "Y", ""}, + // When encoding one byte, the Y (90) becomes a 6bit value (the W) and a + // 2bit value (the first 2 bits of the Q. Q is the 16th b64 digit, it is + // 0b010000. For encoding Y, only those first two bits matter. In + // Strict() mode, the rest must be 0s. So using R (0b010001) should + // fail. + {"WR==", "StdEncoding", "Y", "byte 2"}, } template := `byte 0x%s; byte 0x%s; base64_decode %s; ==` diff --git a/data/transactions/logic/export_test.go b/data/transactions/logic/export_test.go index d0ca904b2..6575fa3bc 100644 --- a/data/transactions/logic/export_test.go +++ b/data/transactions/logic/export_test.go @@ -18,7 +18,9 @@ package logic // Export for testing only. See // https://medium.com/@robiplus/golang-trick-export-for-test-aa16cbd7b8cd for a -// nice explanation. +// nice explanation. tl;dr: Since some of our testing is in logic_test package, +// we export some extra things to make testing easier there. But we do it in a +// _test.go file, so they are only exported during testing. func NewExpect(l int, s string) Expect { return Expect{l, s} diff --git a/data/transactions/logic/fields_test.go b/data/transactions/logic/fields_test.go index c3fe020b3..b6d1a4989 100644 --- a/data/transactions/logic/fields_test.go +++ b/data/transactions/logic/fields_test.go @@ -64,7 +64,7 @@ func TestGlobalFieldsVersions(t *testing.T) { ep.Ledger = ledger // check failure with version check - _, err := EvalApp(ops.Program, 0, 0, ep) + _, err := EvalApp(ops.Program, 0, 888, ep) require.Error(t, err) require.Contains(t, err.Error(), "greater than protocol supported version") @@ -191,7 +191,7 @@ func TestTxnEffectsAvailable(t *testing.T) { _, err := EvalSignature(1, ep) require.Error(t, err) ep.Ledger = MakeLedger(nil) - _, err = EvalApp(ops.Program, 1, 0, ep) + _, err = EvalApp(ops.Program, 1, 888, ep) if v < txnEffectsVersion { require.Error(t, err, source) } else { diff --git a/data/transactions/logic/ledger_test.go b/data/transactions/logic/ledger_test.go index 26b959bcc..7efdeebbd 100644 --- a/data/transactions/logic/ledger_test.go +++ b/data/transactions/logic/ledger_test.go @@ -21,7 +21,6 @@ import ( "fmt" "math/rand" - "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/protocol" @@ -95,10 +94,9 @@ func (l *Ledger) NewAccount(addr basics.Address, balance uint64) { l.balances[addr] = makeBalanceRecord(addr, balance) } -// NewApp add a new AVM app to the Ledger, and arranges so that future -// executions will act as though they are that app. In most uses, it only sets -// up the id and schema but no code, as testing will want to try many different -// code sequences. +// NewApp add a new AVM app to the Ledger. In most uses, it only sets up the id +// and schema but no code, as testing will want to try many different code +// sequences. func (l *Ledger) NewApp(creator basics.Address, appID basics.AppIndex, params basics.AppParams) { params = params.Clone() if params.GlobalState == nil { @@ -198,48 +196,46 @@ func (l *Ledger) LatestTimestamp() int64 { return int64(rand.Uint32() + 1) } -// Balance returns the value in an account, as MicroAlgos -func (l *Ledger) Balance(addr basics.Address) (amount basics.MicroAlgos, err error) { - br, ok := l.balances[addr] - if !ok { - return basics.MicroAlgos{Raw: 0}, nil - } - return basics.MicroAlgos{Raw: br.balance}, nil -} - -// MinBalance computes the MinBalance requirement for an account, -// under the given consensus parameters. -func (l *Ledger) MinBalance(addr basics.Address, proto *config.ConsensusParams) (amount basics.MicroAlgos, err error) { - br, ok := l.balances[addr] - if !ok { - br = makeBalanceRecord(addr, 0) +// AccountData returns a version of the account that is good enough for +// satisfiying AVM needs. (balance, calc minbalance, and authaddr) +func (l *Ledger) AccountData(addr basics.Address) (basics.AccountData, error) { + br := l.balances[addr] + // br may come back empty if addr doesn't exist. That's fine for our needs. + assets := make(map[basics.AssetIndex]basics.AssetParams) + for a, p := range l.assets { + if p.Creator == addr { + assets[a] = p.AssetParams + } } - var min uint64 - - // First, base MinBalance - min = proto.MinBalance + schemaTotal := basics.StateSchema{} + pagesTotal := uint32(0) - // MinBalance for each Asset - assetCost := basics.MulSaturate(proto.MinBalance, uint64(len(br.holdings))) - min = basics.AddSaturate(min, assetCost) - - // Base MinBalance + GlobalStateSchema.MinBalance + ExtraProgramPages MinBalance for each created application - for _, params := range l.applications { - if params.Creator == addr { - min = basics.AddSaturate(min, proto.AppFlatParamsMinBalance) - min = basics.AddSaturate(min, params.GlobalStateSchema.MinBalance(proto).Raw) - min = basics.AddSaturate(min, basics.MulSaturate(proto.AppFlatParamsMinBalance, uint64(params.ExtraProgramPages))) + apps := make(map[basics.AppIndex]basics.AppParams) + for a, p := range l.applications { + if p.Creator == addr { + apps[a] = p.AppParams + schemaTotal = schemaTotal.AddSchema(p.GlobalStateSchema) + pagesTotal = p.ExtraProgramPages } } - // Base MinBalance + LocalStateSchema.MinBalance for each opted in application - for idx := range br.locals { - min = basics.AddSaturate(min, proto.AppFlatParamsMinBalance) - min = basics.AddSaturate(min, l.applications[idx].LocalStateSchema.MinBalance(proto).Raw) + locals := map[basics.AppIndex]basics.AppLocalState{} + for a := range br.locals { + locals[a] = basics.AppLocalState{} // No need to fill in + schemaTotal = schemaTotal.AddSchema(l.applications[a].LocalStateSchema) } - return basics.MicroAlgos{Raw: min}, nil + return basics.AccountData{ + MicroAlgos: basics.MicroAlgos{Raw: br.balance}, + AssetParams: assets, + Assets: br.holdings, + AuthAddr: br.auth, + AppLocalStates: locals, + AppParams: apps, + TotalAppSchema: schemaTotal, + TotalExtraAppPages: pagesTotal, + }, nil } // Authorizer returns the address that must authorize txns from a diff --git a/ledger/internal/applications.go b/ledger/internal/applications.go index 5fef80a21..1c9502c5a 100644 --- a/ledger/internal/applications.go +++ b/ledger/internal/applications.go @@ -19,7 +19,6 @@ package internal import ( "fmt" - "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/transactions/logic" @@ -53,23 +52,12 @@ func newLogicLedger(cow cowForLogicLedger) *logicLedger { } } -func (al *logicLedger) Balance(addr basics.Address) (res basics.MicroAlgos, err error) { - // Fetch record with pending rewards applied +func (al *logicLedger) AccountData(addr basics.Address) (basics.AccountData, error) { record, err := al.cow.Get(addr, true) if err != nil { - return + return basics.AccountData{}, err } - - return record.MicroAlgos, nil -} - -func (al *logicLedger) MinBalance(addr basics.Address, proto *config.ConsensusParams) (res basics.MicroAlgos, err error) { - record, err := al.cow.Get(addr, false) // pending rewards unneeded - if err != nil { - return - } - - return record.MinBalance(proto), nil + return record, nil } func (al *logicLedger) Authorizer(addr basics.Address) (basics.Address, error) { diff --git a/ledger/internal/applications_test.go b/ledger/internal/applications_test.go index c53989757..2ea442171 100644 --- a/ledger/internal/applications_test.go +++ b/ledger/internal/applications_test.go @@ -145,9 +145,9 @@ func TestLogicLedgerBalances(t *testing.T) { addr1 := ledgertesting.RandomAddress() ble := basics.MicroAlgos{Raw: 100} c.brs = map[basics.Address]basics.AccountData{addr1: {MicroAlgos: ble}} - bla, err := l.Balance(addr1) + acct, err := l.AccountData(addr1) a.NoError(err) - a.Equal(ble, bla) + a.Equal(ble, acct.MicroAlgos) } func TestLogicLedgerGetters(t *testing.T) { |