From 17fdd31fdb609d2d24c0274754d59a24d5c9b8c6 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Wed, 8 Sep 2021 20:57:21 -0400 Subject: [PATCH 01/28] Increment TxnCounter with inner transactions This only changes the blocks, so there is still a lot of work to do so that external APIs see things properly. The REST API assumes that it can determine a creatble ID based on the index of a transaction in a block and the block's TxnCounter. That is no longer true, it must actually figure out how many inner txns it is skipping over. --- ledger/apptxn_test.go | 48 +++++++++++++++++++++++++++++++++++++++++++ ledger/cow.go | 11 ++++++++-- ledger/eval.go | 2 +- 3 files changed, 58 insertions(+), 3 deletions(-) diff --git a/ledger/apptxn_test.go b/ledger/apptxn_test.go index b93604ca46..6e885bf55b 100644 --- a/ledger/apptxn_test.go +++ b/ledger/apptxn_test.go @@ -624,3 +624,51 @@ func TestRekeyActionCloseAccount(t *testing.T) { eval.txn(t, &useacct, "unauthorized") l.endBlock(t, eval) } + +// TestInnerTxCount ensures that inner transactions increment the TxnCounter +func TestInnerTxnCount(t *testing.T) { + partitiontest.PartitionTest(t) + + genBalances, addrs, _ := newTestGenesis() + l := newTestLedger(t, genBalances) + defer l.Close() + + create := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApprovalProgram: main(` + tx_begin + int pay + tx_field TypeEnum + int 5000 + tx_field Amount + txn Accounts 1 + tx_field Receiver + tx_submit +`), + } + + fund := txntest.Txn{ + Type: "pay", + Sender: addrs[0], + Receiver: basics.AppIndex(1).Address(), + Amount: 200000, // account min balance, plus fees + } + + payout1 := txntest.Txn{ + Type: "appl", + Sender: addrs[1], + ApplicationID: basics.AppIndex(1), + Accounts: []basics.Address{addrs[1]}, // pay self + } + + eval := l.nextBlock(t) + eval.txns(t, &create, &fund) + vb := l.endBlock(t, eval) + require.Equal(t, 2, int(vb.blk.TxnCounter)) + + eval = l.nextBlock(t) + eval.txns(t, &payout1) + vb = l.endBlock(t, eval) + require.Equal(t, 4, int(vb.blk.TxnCounter)) +} diff --git a/ledger/cow.go b/ledger/cow.go index e618a59196..91b4089fe9 100644 --- a/ledger/cow.go +++ b/ledger/cow.go @@ -57,6 +57,10 @@ type roundCowState struct { proto config.ConsensusParams mods ledgercore.StateDelta + // count of transactions. Formerly, we used len(cb.mods), but that + // does not count inner transactions. + txnCount uint64 + // storage deltas populated as side effects of AppCall transaction // 1. Opt-in/Close actions (see Allocate/Deallocate) // 2. Stateful TEAL evaluation (see SetKey/DelKey) @@ -180,7 +184,7 @@ func (cb *roundCowState) checkDup(firstValid, lastValid basics.Round, txid trans } func (cb *roundCowState) txnCounter() uint64 { - return cb.lookupParent.txnCounter() + uint64(len(cb.mods.Txids)) + return cb.lookupParent.txnCounter() + cb.txnCount } func (cb *roundCowState) compactCertNext() basics.Round { @@ -198,8 +202,9 @@ func (cb *roundCowState) trackCreatable(creatableIndex basics.CreatableIndex) { cb.trackedCreatables[cb.groupIdx] = creatableIndex } -func (cb *roundCowState) addTx(txn transactions.Transaction, txid transactions.Txid) { +func (cb *roundCowState) addTx(txn transactions.Transaction, txid transactions.Txid, inners uint64) { cb.mods.Txids[txid] = txn.LastValid + cb.txnCount += 1 + inners if txn.Lease != [32]byte{} { cb.mods.Txleases[ledgercore.Txlease{Sender: txn.Sender, Lease: txn.Lease}] = txn.LastValid } @@ -242,6 +247,8 @@ func (cb *roundCowState) commitToParent() { for txid, lv := range cb.mods.Txids { cb.commitParent.mods.Txids[txid] = lv } + cb.commitParent.txnCount += cb.txnCount + for txl, expires := range cb.mods.Txleases { cb.commitParent.mods.Txleases[txl] = expires } diff --git a/ledger/eval.go b/ledger/eval.go index 20b5a3cd32..8544f5af34 100644 --- a/ledger/eval.go +++ b/ledger/eval.go @@ -905,7 +905,7 @@ func (eval *BlockEvaluator) transaction(txn transactions.SignedTxn, evalParams * } // Remember this txn - cow.addTx(txn.Txn, txid) + cow.addTx(txn.Txn, txid, uint64(len(applyData.EvalDelta.InnerTxns))) // Will need to be recursive when inners are return nil } From 95c6da77bdd668062e10d6e9e9f0f73dc1e959cf Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Thu, 9 Sep 2021 19:37:22 -0400 Subject: [PATCH 02/28] Add creatable IDs to ApplyData and REST API. --- config/consensus.go | 5 +++ daemon/algod/api/server/v2/utils.go | 18 +++++++++ data/transactions/transaction.go | 11 ++++++ data/transactions/verify/txn.go | 4 +- ledger/apply/application.go | 5 +++ ledger/apply/asset.go | 7 ++++ ledger/apptxn_test.go | 61 +++++++++++++++++++++++++++++ ledger/eval.go | 2 +- 8 files changed, 110 insertions(+), 3 deletions(-) diff --git a/config/consensus.go b/config/consensus.go index 30a655b1e7..d04fd294f1 100644 --- a/config/consensus.go +++ b/config/consensus.go @@ -1022,6 +1022,11 @@ func initConsensusProtocols() { // Enable App calls to pool budget in grouped transactions vFuture.EnableAppCostPooling = true + + // Enable Inner Transactions, and set maximum number. 0 value is + // disabled. Value > 0 also activates storage of creatable IDs in + // ApplyData, as that is required to support REST API when inner + // transactions are activated. vFuture.MaxInnerTransactions = 16 // Allow 50 app opt ins diff --git a/daemon/algod/api/server/v2/utils.go b/daemon/algod/api/server/v2/utils.go index 84e7d70a03..8074370a2e 100644 --- a/daemon/algod/api/server/v2/utils.go +++ b/daemon/algod/api/server/v2/utils.go @@ -128,6 +128,15 @@ func computeAssetIndexFromTxn(tx node.TxnWithStatus, l *data.Ledger) (aidx *uint return nil } + aid := uint64(tx.ApplyData.ConfigAsset) + if aid > 0 { + return &aid + } + // If there is no ConfigAsset in the ApplyData, it must be a + // transaction before inner transactions were activated. Therefore + // the computeCreatableIndexInPayset function will work properly + // to deduce the aid. Proceed. + // Look up block where transaction was confirmed blk, err := l.Block(tx.ConfirmedRound) if err != nil { @@ -163,6 +172,15 @@ func computeAppIndexFromTxn(tx node.TxnWithStatus, l *data.Ledger) (aidx *uint64 return nil } + aid := uint64(tx.ApplyData.ApplicationID) + if aid > 0 { + return &aid + } + // If there is no ApplicationID in the ApplyData, it must be a + // transaction before inner transactions were activated. Therefore + // the computeCreatableIndexInPayset function will work properly + // to deduce the aid. Proceed. + // Look up block where transaction was confirmed blk, err := l.Block(tx.ConfirmedRound) if err != nil { diff --git a/data/transactions/transaction.go b/data/transactions/transaction.go index 923e794785..2a5fe327a9 100644 --- a/data/transactions/transaction.go +++ b/data/transactions/transaction.go @@ -113,6 +113,11 @@ type ApplyData struct { ReceiverRewards basics.MicroAlgos `codec:"rr"` CloseRewards basics.MicroAlgos `codec:"rc"` EvalDelta EvalDelta `codec:"dt"` + + // If asa or app is being created, the id used. Else 0. + // Names chosen to match naming the corresponding txn. + ConfigAsset basics.AssetIndex `codec:"caid"` + ApplicationID basics.AppIndex `codec:"apid"` } // Equal returns true if two ApplyDatas are equal, ignoring nilness equality on @@ -133,6 +138,12 @@ func (ad ApplyData) Equal(o ApplyData) bool { if ad.CloseRewards != o.CloseRewards { return false } + if ad.ConfigAsset != o.ConfigAsset { + return false + } + if ad.ApplicationID != o.ApplicationID { + return false + } if !ad.EvalDelta.Equal(o.EvalDelta) { return false } diff --git a/data/transactions/verify/txn.go b/data/transactions/verify/txn.go index 671b9a347c..13a07b328a 100644 --- a/data/transactions/verify/txn.go +++ b/data/transactions/verify/txn.go @@ -41,14 +41,14 @@ var logicErrTotal = metrics.MakeCounter(metrics.MetricName{Name: "algod_ledger_l // When doing so, it attempts to break these into smaller "worksets" where each workset takes about 2ms of execution time in order // to avoid context switching overhead while providing good validation cancelation responsiveness. Each one of these worksets is // "populated" with roughly txnPerWorksetThreshold transactions. ( note that the real evaluation time is unknown, but benchmarks -// showen that these are realistic numbers ) +// show that these are realistic numbers ) const txnPerWorksetThreshold = 32 // When the PaysetGroups is generating worksets, it enqueues up to concurrentWorksets entries to the execution pool. This serves several // purposes : // - if the verification task need to be aborted, there are only concurrentWorksets entries that are currently redundant on the execution pool queue. // - that number of concurrent tasks would not get beyond the capacity of the execution pool back buffer. -// - if we were to "redundently" execute all these during context cancelation, we would spent at most 2ms * 16 = 32ms time. +// - if we were to "redundantly" execute all these during context cancelation, we would spent at most 2ms * 16 = 32ms time. // - it allows us to linearly scan the input, and process elements only once we're going to queue them into the pool. const concurrentWorksets = 16 diff --git a/ledger/apply/application.go b/ledger/apply/application.go index fab18a925c..2ff5733030 100644 --- a/ledger/apply/application.go +++ b/ledger/apply/application.go @@ -342,6 +342,11 @@ func ApplicationCall(ac transactions.ApplicationCallTxnFields, header transactio if err != nil { return } + // No separate config for activating storage in AD because + // inner transactions can't be turned on without this change. + if balances.ConsensusParams().MaxInnerTransactions > 0 { + ad.ApplicationID = appIdx + } } // Fetch the application parameters, if they exist diff --git a/ledger/apply/asset.go b/ledger/apply/asset.go index 1fe3c6faca..0796cb8f7f 100644 --- a/ledger/apply/asset.go +++ b/ledger/apply/asset.go @@ -100,6 +100,13 @@ func AssetConfig(cc transactions.AssetConfigTxnFields, header transactions.Heade return err } + // Record the index used. No separate config for activating + // storage in AD because inner transactions can't be turned on + // without this change. + if balances.ConsensusParams().MaxInnerTransactions > 0 { + ad.ConfigAsset = newidx + } + // Tell the cow what asset we created err = balances.AllocateAsset(header.Sender, newidx, true) if err != nil { diff --git a/ledger/apptxn_test.go b/ledger/apptxn_test.go index 6e885bf55b..990f73474b 100644 --- a/ledger/apptxn_test.go +++ b/ledger/apptxn_test.go @@ -625,6 +625,67 @@ func TestRekeyActionCloseAccount(t *testing.T) { l.endBlock(t, eval) } +// TestPayAction ensures a pay in teal affects balances +func TestDuplicatePayAction(t *testing.T) { + partitiontest.PartitionTest(t) + + genBalances, addrs, _ := newTestGenesis() + l := newTestLedger(t, genBalances) + defer l.Close() + + create := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApprovalProgram: main(` + tx_begin + int pay + tx_field TypeEnum + int 5000 + tx_field Amount + txn Accounts 1 + tx_field Receiver + tx_submit + tx_begin + int pay + tx_field TypeEnum + int 5000 + tx_field Amount + txn Accounts 1 + tx_field Receiver + tx_submit +`), + } + + fund := txntest.Txn{ + Type: "pay", + Sender: addrs[0], + Receiver: basics.AppIndex(1).Address(), + Amount: 200000, // account min balance, plus fees + } + + paytwice := txntest.Txn{ + Type: "appl", + Sender: addrs[1], + ApplicationID: basics.AppIndex(1), + Accounts: []basics.Address{addrs[1]}, // pay self + } + + eval := l.nextBlock(t) + eval.txns(t, &create, &fund, &paytwice) + l.endBlock(t, eval) + + ad0 := l.micros(t, addrs[0]) + ad1 := l.micros(t, addrs[1]) + app := l.micros(t, basics.AppIndex(1).Address()) + + // create(1000) and fund(1000 + 200000) + require.Equal(t, 202000, int(genBalances.Balances[addrs[0]].MicroAlgos.Raw-ad0)) + // paid 10000, but 1000 fee on tx + require.Equal(t, 9000, int(ad1-genBalances.Balances[addrs[1]].MicroAlgos.Raw)) + // app still has 188000 (paid out 10000, and paid 2 x fee to do it) + require.Equal(t, 188000, int(app)) +} + // TestInnerTxCount ensures that inner transactions increment the TxnCounter func TestInnerTxnCount(t *testing.T) { partitiontest.PartitionTest(t) diff --git a/ledger/eval.go b/ledger/eval.go index 8544f5af34..53ec72a050 100644 --- a/ledger/eval.go +++ b/ledger/eval.go @@ -1283,7 +1283,7 @@ transactionGroupLoop: return eval.state.deltas(), nil } -// loadedTransactionGroup is a helper struct to allow asyncronious loading of the account data needed by the transaction groups +// loadedTransactionGroup is a helper struct to allow asynchronous loading of the account data needed by the transaction groups type loadedTransactionGroup struct { // group is the transaction group group []transactions.SignedTxnWithAD From 50280bffdab732dcdd5639a1c6bc4bf39e92368e Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Thu, 9 Sep 2021 21:36:26 -0400 Subject: [PATCH 03/28] `make msgp` after adding creatable fields to ApplyData --- data/transactions/msgp_gen.go | 264 ++++++++++++++++++++++++++-------- 1 file changed, 201 insertions(+), 63 deletions(-) diff --git a/data/transactions/msgp_gen.go b/data/transactions/msgp_gen.go index b46a5694da..009de07b5b 100644 --- a/data/transactions/msgp_gen.go +++ b/data/transactions/msgp_gen.go @@ -773,32 +773,40 @@ func (z *ApplicationCallTxnFields) MsgIsZero() bool { func (z *ApplyData) MarshalMsg(b []byte) (o []byte) { o = msgp.Require(b, z.Msgsize()) // omitempty: check for empty values - zb0001Len := uint32(6) - var zb0001Mask uint8 /* 7 bits */ + zb0001Len := uint32(8) + var zb0001Mask uint16 /* 9 bits */ if (*z).AssetClosingAmount == 0 { zb0001Len-- zb0001Mask |= 0x2 } - if (*z).ClosingAmount.MsgIsZero() { + if (*z).ApplicationID.MsgIsZero() { zb0001Len-- zb0001Mask |= 0x4 } - if (*z).EvalDelta.MsgIsZero() { + if (*z).ClosingAmount.MsgIsZero() { zb0001Len-- zb0001Mask |= 0x8 } - if (*z).CloseRewards.MsgIsZero() { + if (*z).ConfigAsset.MsgIsZero() { zb0001Len-- zb0001Mask |= 0x10 } - if (*z).ReceiverRewards.MsgIsZero() { + if (*z).EvalDelta.MsgIsZero() { zb0001Len-- zb0001Mask |= 0x20 } - if (*z).SenderRewards.MsgIsZero() { + if (*z).CloseRewards.MsgIsZero() { zb0001Len-- zb0001Mask |= 0x40 } + if (*z).ReceiverRewards.MsgIsZero() { + zb0001Len-- + zb0001Mask |= 0x80 + } + if (*z).SenderRewards.MsgIsZero() { + zb0001Len-- + zb0001Mask |= 0x100 + } // variable map header, size zb0001Len o = append(o, 0x80|uint8(zb0001Len)) if zb0001Len != 0 { @@ -808,26 +816,36 @@ func (z *ApplyData) MarshalMsg(b []byte) (o []byte) { o = msgp.AppendUint64(o, (*z).AssetClosingAmount) } if (zb0001Mask & 0x4) == 0 { // if not empty + // string "apid" + o = append(o, 0xa4, 0x61, 0x70, 0x69, 0x64) + o = (*z).ApplicationID.MarshalMsg(o) + } + if (zb0001Mask & 0x8) == 0 { // if not empty // string "ca" o = append(o, 0xa2, 0x63, 0x61) o = (*z).ClosingAmount.MarshalMsg(o) } - if (zb0001Mask & 0x8) == 0 { // if not empty + if (zb0001Mask & 0x10) == 0 { // if not empty + // string "caid" + o = append(o, 0xa4, 0x63, 0x61, 0x69, 0x64) + o = (*z).ConfigAsset.MarshalMsg(o) + } + if (zb0001Mask & 0x20) == 0 { // if not empty // string "dt" o = append(o, 0xa2, 0x64, 0x74) o = (*z).EvalDelta.MarshalMsg(o) } - if (zb0001Mask & 0x10) == 0 { // if not empty + if (zb0001Mask & 0x40) == 0 { // if not empty // string "rc" o = append(o, 0xa2, 0x72, 0x63) o = (*z).CloseRewards.MarshalMsg(o) } - if (zb0001Mask & 0x20) == 0 { // if not empty + if (zb0001Mask & 0x80) == 0 { // if not empty // string "rr" o = append(o, 0xa2, 0x72, 0x72) o = (*z).ReceiverRewards.MarshalMsg(o) } - if (zb0001Mask & 0x40) == 0 { // if not empty + if (zb0001Mask & 0x100) == 0 { // if not empty // string "rs" o = append(o, 0xa2, 0x72, 0x73) o = (*z).SenderRewards.MarshalMsg(o) @@ -902,6 +920,22 @@ func (z *ApplyData) UnmarshalMsg(bts []byte) (o []byte, err error) { return } } + if zb0001 > 0 { + zb0001-- + bts, err = (*z).ConfigAsset.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "ConfigAsset") + return + } + } + if zb0001 > 0 { + zb0001-- + bts, err = (*z).ApplicationID.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "ApplicationID") + return + } + } if zb0001 > 0 { err = msgp.ErrTooManyArrayFields(zb0001) if err != nil { @@ -961,6 +995,18 @@ func (z *ApplyData) UnmarshalMsg(bts []byte) (o []byte, err error) { err = msgp.WrapError(err, "EvalDelta") return } + case "caid": + bts, err = (*z).ConfigAsset.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "ConfigAsset") + return + } + case "apid": + bts, err = (*z).ApplicationID.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "ApplicationID") + return + } default: err = msgp.ErrNoField(string(field)) if err != nil { @@ -981,13 +1027,13 @@ func (_ *ApplyData) CanUnmarshalMsg(z interface{}) bool { // Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message func (z *ApplyData) Msgsize() (s int) { - s = 1 + 3 + (*z).ClosingAmount.Msgsize() + 4 + msgp.Uint64Size + 3 + (*z).SenderRewards.Msgsize() + 3 + (*z).ReceiverRewards.Msgsize() + 3 + (*z).CloseRewards.Msgsize() + 3 + (*z).EvalDelta.Msgsize() + s = 1 + 3 + (*z).ClosingAmount.Msgsize() + 4 + msgp.Uint64Size + 3 + (*z).SenderRewards.Msgsize() + 3 + (*z).ReceiverRewards.Msgsize() + 3 + (*z).CloseRewards.Msgsize() + 3 + (*z).EvalDelta.Msgsize() + 5 + (*z).ConfigAsset.Msgsize() + 5 + (*z).ApplicationID.Msgsize() return } // MsgIsZero returns whether this is a zero value func (z *ApplyData) MsgIsZero() bool { - return ((*z).ClosingAmount.MsgIsZero()) && ((*z).AssetClosingAmount == 0) && ((*z).SenderRewards.MsgIsZero()) && ((*z).ReceiverRewards.MsgIsZero()) && ((*z).CloseRewards.MsgIsZero()) && ((*z).EvalDelta.MsgIsZero()) + return ((*z).ClosingAmount.MsgIsZero()) && ((*z).AssetClosingAmount == 0) && ((*z).SenderRewards.MsgIsZero()) && ((*z).ReceiverRewards.MsgIsZero()) && ((*z).CloseRewards.MsgIsZero()) && ((*z).EvalDelta.MsgIsZero()) && ((*z).ConfigAsset.MsgIsZero()) && ((*z).ApplicationID.MsgIsZero()) } // MarshalMsg implements msgp.Marshaler @@ -3320,60 +3366,68 @@ func (z *SignedTxn) MsgIsZero() bool { func (z *SignedTxnInBlock) MarshalMsg(b []byte) (o []byte) { o = msgp.Require(b, z.Msgsize()) // omitempty: check for empty values - zb0001Len := uint32(13) - var zb0001Mask uint32 /* 17 bits */ + zb0001Len := uint32(15) + var zb0001Mask uint32 /* 19 bits */ if (*z).SignedTxnWithAD.ApplyData.AssetClosingAmount == 0 { zb0001Len-- zb0001Mask |= 0x10 } - if (*z).SignedTxnWithAD.ApplyData.ClosingAmount.MsgIsZero() { + if (*z).SignedTxnWithAD.ApplyData.ApplicationID.MsgIsZero() { zb0001Len-- zb0001Mask |= 0x20 } - if (*z).SignedTxnWithAD.ApplyData.EvalDelta.MsgIsZero() { + if (*z).SignedTxnWithAD.ApplyData.ClosingAmount.MsgIsZero() { zb0001Len-- zb0001Mask |= 0x40 } - if (*z).HasGenesisHash == false { + if (*z).SignedTxnWithAD.ApplyData.ConfigAsset.MsgIsZero() { zb0001Len-- zb0001Mask |= 0x80 } - if (*z).HasGenesisID == false { + if (*z).SignedTxnWithAD.ApplyData.EvalDelta.MsgIsZero() { zb0001Len-- zb0001Mask |= 0x100 } - if (*z).SignedTxnWithAD.SignedTxn.Lsig.MsgIsZero() { + if (*z).HasGenesisHash == false { zb0001Len-- zb0001Mask |= 0x200 } - if (*z).SignedTxnWithAD.SignedTxn.Msig.MsgIsZero() { + if (*z).HasGenesisID == false { zb0001Len-- zb0001Mask |= 0x400 } - if (*z).SignedTxnWithAD.ApplyData.CloseRewards.MsgIsZero() { + if (*z).SignedTxnWithAD.SignedTxn.Lsig.MsgIsZero() { zb0001Len-- zb0001Mask |= 0x800 } - if (*z).SignedTxnWithAD.ApplyData.ReceiverRewards.MsgIsZero() { + if (*z).SignedTxnWithAD.SignedTxn.Msig.MsgIsZero() { zb0001Len-- zb0001Mask |= 0x1000 } - if (*z).SignedTxnWithAD.ApplyData.SenderRewards.MsgIsZero() { + if (*z).SignedTxnWithAD.ApplyData.CloseRewards.MsgIsZero() { zb0001Len-- zb0001Mask |= 0x2000 } - if (*z).SignedTxnWithAD.SignedTxn.AuthAddr.MsgIsZero() { + if (*z).SignedTxnWithAD.ApplyData.ReceiverRewards.MsgIsZero() { zb0001Len-- zb0001Mask |= 0x4000 } - if (*z).SignedTxnWithAD.SignedTxn.Sig.MsgIsZero() { + if (*z).SignedTxnWithAD.ApplyData.SenderRewards.MsgIsZero() { zb0001Len-- zb0001Mask |= 0x8000 } - if (*z).SignedTxnWithAD.SignedTxn.Txn.MsgIsZero() { + if (*z).SignedTxnWithAD.SignedTxn.AuthAddr.MsgIsZero() { zb0001Len-- zb0001Mask |= 0x10000 } + if (*z).SignedTxnWithAD.SignedTxn.Sig.MsgIsZero() { + zb0001Len-- + zb0001Mask |= 0x20000 + } + if (*z).SignedTxnWithAD.SignedTxn.Txn.MsgIsZero() { + zb0001Len-- + zb0001Mask |= 0x40000 + } // variable map header, size zb0001Len o = append(o, 0x80|uint8(zb0001Len)) if zb0001Len != 0 { @@ -3383,61 +3437,71 @@ func (z *SignedTxnInBlock) MarshalMsg(b []byte) (o []byte) { o = msgp.AppendUint64(o, (*z).SignedTxnWithAD.ApplyData.AssetClosingAmount) } if (zb0001Mask & 0x20) == 0 { // if not empty + // string "apid" + o = append(o, 0xa4, 0x61, 0x70, 0x69, 0x64) + o = (*z).SignedTxnWithAD.ApplyData.ApplicationID.MarshalMsg(o) + } + if (zb0001Mask & 0x40) == 0 { // if not empty // string "ca" o = append(o, 0xa2, 0x63, 0x61) o = (*z).SignedTxnWithAD.ApplyData.ClosingAmount.MarshalMsg(o) } - if (zb0001Mask & 0x40) == 0 { // if not empty + if (zb0001Mask & 0x80) == 0 { // if not empty + // string "caid" + o = append(o, 0xa4, 0x63, 0x61, 0x69, 0x64) + o = (*z).SignedTxnWithAD.ApplyData.ConfigAsset.MarshalMsg(o) + } + if (zb0001Mask & 0x100) == 0 { // if not empty // string "dt" o = append(o, 0xa2, 0x64, 0x74) o = (*z).SignedTxnWithAD.ApplyData.EvalDelta.MarshalMsg(o) } - if (zb0001Mask & 0x80) == 0 { // if not empty + if (zb0001Mask & 0x200) == 0 { // if not empty // string "hgh" o = append(o, 0xa3, 0x68, 0x67, 0x68) o = msgp.AppendBool(o, (*z).HasGenesisHash) } - if (zb0001Mask & 0x100) == 0 { // if not empty + if (zb0001Mask & 0x400) == 0 { // if not empty // string "hgi" o = append(o, 0xa3, 0x68, 0x67, 0x69) o = msgp.AppendBool(o, (*z).HasGenesisID) } - if (zb0001Mask & 0x200) == 0 { // if not empty + if (zb0001Mask & 0x800) == 0 { // if not empty // string "lsig" o = append(o, 0xa4, 0x6c, 0x73, 0x69, 0x67) o = (*z).SignedTxnWithAD.SignedTxn.Lsig.MarshalMsg(o) } - if (zb0001Mask & 0x400) == 0 { // if not empty + if (zb0001Mask & 0x1000) == 0 { // if not empty // string "msig" o = append(o, 0xa4, 0x6d, 0x73, 0x69, 0x67) o = (*z).SignedTxnWithAD.SignedTxn.Msig.MarshalMsg(o) } - if (zb0001Mask & 0x800) == 0 { // if not empty + if (zb0001Mask & 0x2000) == 0 { // if not empty // string "rc" o = append(o, 0xa2, 0x72, 0x63) o = (*z).SignedTxnWithAD.ApplyData.CloseRewards.MarshalMsg(o) } - if (zb0001Mask & 0x1000) == 0 { // if not empty + if (zb0001Mask & 0x4000) == 0 { // if not empty // string "rr" o = append(o, 0xa2, 0x72, 0x72) o = (*z).SignedTxnWithAD.ApplyData.ReceiverRewards.MarshalMsg(o) } - if (zb0001Mask & 0x2000) == 0 { // if not empty + if (zb0001Mask & 0x8000) == 0 { // if not empty // string "rs" o = append(o, 0xa2, 0x72, 0x73) o = (*z).SignedTxnWithAD.ApplyData.SenderRewards.MarshalMsg(o) } - if (zb0001Mask & 0x4000) == 0 { // if not empty + if (zb0001Mask & 0x10000) == 0 { // if not empty // string "sgnr" o = append(o, 0xa4, 0x73, 0x67, 0x6e, 0x72) o = (*z).SignedTxnWithAD.SignedTxn.AuthAddr.MarshalMsg(o) } - if (zb0001Mask & 0x8000) == 0 { // if not empty + if (zb0001Mask & 0x20000) == 0 { // if not empty // string "sig" o = append(o, 0xa3, 0x73, 0x69, 0x67) o = (*z).SignedTxnWithAD.SignedTxn.Sig.MarshalMsg(o) } - if (zb0001Mask & 0x10000) == 0 { // if not empty + if (zb0001Mask & 0x40000) == 0 { // if not empty // string "txn" o = append(o, 0xa3, 0x74, 0x78, 0x6e) o = (*z).SignedTxnWithAD.SignedTxn.Txn.MarshalMsg(o) @@ -3552,6 +3616,22 @@ func (z *SignedTxnInBlock) UnmarshalMsg(bts []byte) (o []byte, err error) { return } } + if zb0001 > 0 { + zb0001-- + bts, err = (*z).SignedTxnWithAD.ApplyData.ConfigAsset.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "ConfigAsset") + return + } + } + if zb0001 > 0 { + zb0001-- + bts, err = (*z).SignedTxnWithAD.ApplyData.ApplicationID.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "ApplicationID") + return + } + } if zb0001 > 0 { zb0001-- (*z).HasGenesisID, bts, err = msgp.ReadBoolBytes(bts) @@ -3657,6 +3737,18 @@ func (z *SignedTxnInBlock) UnmarshalMsg(bts []byte) (o []byte, err error) { err = msgp.WrapError(err, "EvalDelta") return } + case "caid": + bts, err = (*z).SignedTxnWithAD.ApplyData.ConfigAsset.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "ConfigAsset") + return + } + case "apid": + bts, err = (*z).SignedTxnWithAD.ApplyData.ApplicationID.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "ApplicationID") + return + } case "hgi": (*z).HasGenesisID, bts, err = msgp.ReadBoolBytes(bts) if err != nil { @@ -3689,65 +3781,73 @@ func (_ *SignedTxnInBlock) CanUnmarshalMsg(z interface{}) bool { // Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message func (z *SignedTxnInBlock) Msgsize() (s int) { - s = 1 + 4 + (*z).SignedTxnWithAD.SignedTxn.Sig.Msgsize() + 5 + (*z).SignedTxnWithAD.SignedTxn.Msig.Msgsize() + 5 + (*z).SignedTxnWithAD.SignedTxn.Lsig.Msgsize() + 4 + (*z).SignedTxnWithAD.SignedTxn.Txn.Msgsize() + 5 + (*z).SignedTxnWithAD.SignedTxn.AuthAddr.Msgsize() + 3 + (*z).SignedTxnWithAD.ApplyData.ClosingAmount.Msgsize() + 4 + msgp.Uint64Size + 3 + (*z).SignedTxnWithAD.ApplyData.SenderRewards.Msgsize() + 3 + (*z).SignedTxnWithAD.ApplyData.ReceiverRewards.Msgsize() + 3 + (*z).SignedTxnWithAD.ApplyData.CloseRewards.Msgsize() + 3 + (*z).SignedTxnWithAD.ApplyData.EvalDelta.Msgsize() + 4 + msgp.BoolSize + 4 + msgp.BoolSize + s = 1 + 4 + (*z).SignedTxnWithAD.SignedTxn.Sig.Msgsize() + 5 + (*z).SignedTxnWithAD.SignedTxn.Msig.Msgsize() + 5 + (*z).SignedTxnWithAD.SignedTxn.Lsig.Msgsize() + 4 + (*z).SignedTxnWithAD.SignedTxn.Txn.Msgsize() + 5 + (*z).SignedTxnWithAD.SignedTxn.AuthAddr.Msgsize() + 3 + (*z).SignedTxnWithAD.ApplyData.ClosingAmount.Msgsize() + 4 + msgp.Uint64Size + 3 + (*z).SignedTxnWithAD.ApplyData.SenderRewards.Msgsize() + 3 + (*z).SignedTxnWithAD.ApplyData.ReceiverRewards.Msgsize() + 3 + (*z).SignedTxnWithAD.ApplyData.CloseRewards.Msgsize() + 3 + (*z).SignedTxnWithAD.ApplyData.EvalDelta.Msgsize() + 5 + (*z).SignedTxnWithAD.ApplyData.ConfigAsset.Msgsize() + 5 + (*z).SignedTxnWithAD.ApplyData.ApplicationID.Msgsize() + 4 + msgp.BoolSize + 4 + msgp.BoolSize return } // MsgIsZero returns whether this is a zero value func (z *SignedTxnInBlock) MsgIsZero() bool { - return ((*z).SignedTxnWithAD.SignedTxn.Sig.MsgIsZero()) && ((*z).SignedTxnWithAD.SignedTxn.Msig.MsgIsZero()) && ((*z).SignedTxnWithAD.SignedTxn.Lsig.MsgIsZero()) && ((*z).SignedTxnWithAD.SignedTxn.Txn.MsgIsZero()) && ((*z).SignedTxnWithAD.SignedTxn.AuthAddr.MsgIsZero()) && ((*z).SignedTxnWithAD.ApplyData.ClosingAmount.MsgIsZero()) && ((*z).SignedTxnWithAD.ApplyData.AssetClosingAmount == 0) && ((*z).SignedTxnWithAD.ApplyData.SenderRewards.MsgIsZero()) && ((*z).SignedTxnWithAD.ApplyData.ReceiverRewards.MsgIsZero()) && ((*z).SignedTxnWithAD.ApplyData.CloseRewards.MsgIsZero()) && ((*z).SignedTxnWithAD.ApplyData.EvalDelta.MsgIsZero()) && ((*z).HasGenesisID == false) && ((*z).HasGenesisHash == false) + return ((*z).SignedTxnWithAD.SignedTxn.Sig.MsgIsZero()) && ((*z).SignedTxnWithAD.SignedTxn.Msig.MsgIsZero()) && ((*z).SignedTxnWithAD.SignedTxn.Lsig.MsgIsZero()) && ((*z).SignedTxnWithAD.SignedTxn.Txn.MsgIsZero()) && ((*z).SignedTxnWithAD.SignedTxn.AuthAddr.MsgIsZero()) && ((*z).SignedTxnWithAD.ApplyData.ClosingAmount.MsgIsZero()) && ((*z).SignedTxnWithAD.ApplyData.AssetClosingAmount == 0) && ((*z).SignedTxnWithAD.ApplyData.SenderRewards.MsgIsZero()) && ((*z).SignedTxnWithAD.ApplyData.ReceiverRewards.MsgIsZero()) && ((*z).SignedTxnWithAD.ApplyData.CloseRewards.MsgIsZero()) && ((*z).SignedTxnWithAD.ApplyData.EvalDelta.MsgIsZero()) && ((*z).SignedTxnWithAD.ApplyData.ConfigAsset.MsgIsZero()) && ((*z).SignedTxnWithAD.ApplyData.ApplicationID.MsgIsZero()) && ((*z).HasGenesisID == false) && ((*z).HasGenesisHash == false) } // MarshalMsg implements msgp.Marshaler func (z *SignedTxnWithAD) MarshalMsg(b []byte) (o []byte) { o = msgp.Require(b, z.Msgsize()) // omitempty: check for empty values - zb0001Len := uint32(11) - var zb0001Mask uint16 /* 14 bits */ + zb0001Len := uint32(13) + var zb0001Mask uint16 /* 16 bits */ if (*z).ApplyData.AssetClosingAmount == 0 { zb0001Len-- zb0001Mask |= 0x8 } - if (*z).ApplyData.ClosingAmount.MsgIsZero() { + if (*z).ApplyData.ApplicationID.MsgIsZero() { zb0001Len-- zb0001Mask |= 0x10 } - if (*z).ApplyData.EvalDelta.MsgIsZero() { + if (*z).ApplyData.ClosingAmount.MsgIsZero() { zb0001Len-- zb0001Mask |= 0x20 } - if (*z).SignedTxn.Lsig.MsgIsZero() { + if (*z).ApplyData.ConfigAsset.MsgIsZero() { zb0001Len-- zb0001Mask |= 0x40 } - if (*z).SignedTxn.Msig.MsgIsZero() { + if (*z).ApplyData.EvalDelta.MsgIsZero() { zb0001Len-- zb0001Mask |= 0x80 } - if (*z).ApplyData.CloseRewards.MsgIsZero() { + if (*z).SignedTxn.Lsig.MsgIsZero() { zb0001Len-- zb0001Mask |= 0x100 } - if (*z).ApplyData.ReceiverRewards.MsgIsZero() { + if (*z).SignedTxn.Msig.MsgIsZero() { zb0001Len-- zb0001Mask |= 0x200 } - if (*z).ApplyData.SenderRewards.MsgIsZero() { + if (*z).ApplyData.CloseRewards.MsgIsZero() { zb0001Len-- zb0001Mask |= 0x400 } - if (*z).SignedTxn.AuthAddr.MsgIsZero() { + if (*z).ApplyData.ReceiverRewards.MsgIsZero() { zb0001Len-- zb0001Mask |= 0x800 } - if (*z).SignedTxn.Sig.MsgIsZero() { + if (*z).ApplyData.SenderRewards.MsgIsZero() { zb0001Len-- zb0001Mask |= 0x1000 } - if (*z).SignedTxn.Txn.MsgIsZero() { + if (*z).SignedTxn.AuthAddr.MsgIsZero() { zb0001Len-- zb0001Mask |= 0x2000 } + if (*z).SignedTxn.Sig.MsgIsZero() { + zb0001Len-- + zb0001Mask |= 0x4000 + } + if (*z).SignedTxn.Txn.MsgIsZero() { + zb0001Len-- + zb0001Mask |= 0x8000 + } // variable map header, size zb0001Len o = append(o, 0x80|uint8(zb0001Len)) if zb0001Len != 0 { @@ -3757,51 +3857,61 @@ func (z *SignedTxnWithAD) MarshalMsg(b []byte) (o []byte) { o = msgp.AppendUint64(o, (*z).ApplyData.AssetClosingAmount) } if (zb0001Mask & 0x10) == 0 { // if not empty + // string "apid" + o = append(o, 0xa4, 0x61, 0x70, 0x69, 0x64) + o = (*z).ApplyData.ApplicationID.MarshalMsg(o) + } + if (zb0001Mask & 0x20) == 0 { // if not empty // string "ca" o = append(o, 0xa2, 0x63, 0x61) o = (*z).ApplyData.ClosingAmount.MarshalMsg(o) } - if (zb0001Mask & 0x20) == 0 { // if not empty + if (zb0001Mask & 0x40) == 0 { // if not empty + // string "caid" + o = append(o, 0xa4, 0x63, 0x61, 0x69, 0x64) + o = (*z).ApplyData.ConfigAsset.MarshalMsg(o) + } + if (zb0001Mask & 0x80) == 0 { // if not empty // string "dt" o = append(o, 0xa2, 0x64, 0x74) o = (*z).ApplyData.EvalDelta.MarshalMsg(o) } - if (zb0001Mask & 0x40) == 0 { // if not empty + if (zb0001Mask & 0x100) == 0 { // if not empty // string "lsig" o = append(o, 0xa4, 0x6c, 0x73, 0x69, 0x67) o = (*z).SignedTxn.Lsig.MarshalMsg(o) } - if (zb0001Mask & 0x80) == 0 { // if not empty + if (zb0001Mask & 0x200) == 0 { // if not empty // string "msig" o = append(o, 0xa4, 0x6d, 0x73, 0x69, 0x67) o = (*z).SignedTxn.Msig.MarshalMsg(o) } - if (zb0001Mask & 0x100) == 0 { // if not empty + if (zb0001Mask & 0x400) == 0 { // if not empty // string "rc" o = append(o, 0xa2, 0x72, 0x63) o = (*z).ApplyData.CloseRewards.MarshalMsg(o) } - if (zb0001Mask & 0x200) == 0 { // if not empty + if (zb0001Mask & 0x800) == 0 { // if not empty // string "rr" o = append(o, 0xa2, 0x72, 0x72) o = (*z).ApplyData.ReceiverRewards.MarshalMsg(o) } - if (zb0001Mask & 0x400) == 0 { // if not empty + if (zb0001Mask & 0x1000) == 0 { // if not empty // string "rs" o = append(o, 0xa2, 0x72, 0x73) o = (*z).ApplyData.SenderRewards.MarshalMsg(o) } - if (zb0001Mask & 0x800) == 0 { // if not empty + if (zb0001Mask & 0x2000) == 0 { // if not empty // string "sgnr" o = append(o, 0xa4, 0x73, 0x67, 0x6e, 0x72) o = (*z).SignedTxn.AuthAddr.MarshalMsg(o) } - if (zb0001Mask & 0x1000) == 0 { // if not empty + if (zb0001Mask & 0x4000) == 0 { // if not empty // string "sig" o = append(o, 0xa3, 0x73, 0x69, 0x67) o = (*z).SignedTxn.Sig.MarshalMsg(o) } - if (zb0001Mask & 0x2000) == 0 { // if not empty + if (zb0001Mask & 0x8000) == 0 { // if not empty // string "txn" o = append(o, 0xa3, 0x74, 0x78, 0x6e) o = (*z).SignedTxn.Txn.MarshalMsg(o) @@ -3916,6 +4026,22 @@ func (z *SignedTxnWithAD) UnmarshalMsg(bts []byte) (o []byte, err error) { return } } + if zb0001 > 0 { + zb0001-- + bts, err = (*z).ApplyData.ConfigAsset.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "ConfigAsset") + return + } + } + if zb0001 > 0 { + zb0001-- + bts, err = (*z).ApplyData.ApplicationID.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "ApplicationID") + return + } + } if zb0001 > 0 { err = msgp.ErrTooManyArrayFields(zb0001) if err != nil { @@ -4005,6 +4131,18 @@ func (z *SignedTxnWithAD) UnmarshalMsg(bts []byte) (o []byte, err error) { err = msgp.WrapError(err, "EvalDelta") return } + case "caid": + bts, err = (*z).ApplyData.ConfigAsset.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "ConfigAsset") + return + } + case "apid": + bts, err = (*z).ApplyData.ApplicationID.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "ApplicationID") + return + } default: err = msgp.ErrNoField(string(field)) if err != nil { @@ -4025,13 +4163,13 @@ func (_ *SignedTxnWithAD) CanUnmarshalMsg(z interface{}) bool { // Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message func (z *SignedTxnWithAD) Msgsize() (s int) { - s = 1 + 4 + (*z).SignedTxn.Sig.Msgsize() + 5 + (*z).SignedTxn.Msig.Msgsize() + 5 + (*z).SignedTxn.Lsig.Msgsize() + 4 + (*z).SignedTxn.Txn.Msgsize() + 5 + (*z).SignedTxn.AuthAddr.Msgsize() + 3 + (*z).ApplyData.ClosingAmount.Msgsize() + 4 + msgp.Uint64Size + 3 + (*z).ApplyData.SenderRewards.Msgsize() + 3 + (*z).ApplyData.ReceiverRewards.Msgsize() + 3 + (*z).ApplyData.CloseRewards.Msgsize() + 3 + (*z).ApplyData.EvalDelta.Msgsize() + s = 1 + 4 + (*z).SignedTxn.Sig.Msgsize() + 5 + (*z).SignedTxn.Msig.Msgsize() + 5 + (*z).SignedTxn.Lsig.Msgsize() + 4 + (*z).SignedTxn.Txn.Msgsize() + 5 + (*z).SignedTxn.AuthAddr.Msgsize() + 3 + (*z).ApplyData.ClosingAmount.Msgsize() + 4 + msgp.Uint64Size + 3 + (*z).ApplyData.SenderRewards.Msgsize() + 3 + (*z).ApplyData.ReceiverRewards.Msgsize() + 3 + (*z).ApplyData.CloseRewards.Msgsize() + 3 + (*z).ApplyData.EvalDelta.Msgsize() + 5 + (*z).ApplyData.ConfigAsset.Msgsize() + 5 + (*z).ApplyData.ApplicationID.Msgsize() return } // MsgIsZero returns whether this is a zero value func (z *SignedTxnWithAD) MsgIsZero() bool { - return ((*z).SignedTxn.Sig.MsgIsZero()) && ((*z).SignedTxn.Msig.MsgIsZero()) && ((*z).SignedTxn.Lsig.MsgIsZero()) && ((*z).SignedTxn.Txn.MsgIsZero()) && ((*z).SignedTxn.AuthAddr.MsgIsZero()) && ((*z).ApplyData.ClosingAmount.MsgIsZero()) && ((*z).ApplyData.AssetClosingAmount == 0) && ((*z).ApplyData.SenderRewards.MsgIsZero()) && ((*z).ApplyData.ReceiverRewards.MsgIsZero()) && ((*z).ApplyData.CloseRewards.MsgIsZero()) && ((*z).ApplyData.EvalDelta.MsgIsZero()) + return ((*z).SignedTxn.Sig.MsgIsZero()) && ((*z).SignedTxn.Msig.MsgIsZero()) && ((*z).SignedTxn.Lsig.MsgIsZero()) && ((*z).SignedTxn.Txn.MsgIsZero()) && ((*z).SignedTxn.AuthAddr.MsgIsZero()) && ((*z).ApplyData.ClosingAmount.MsgIsZero()) && ((*z).ApplyData.AssetClosingAmount == 0) && ((*z).ApplyData.SenderRewards.MsgIsZero()) && ((*z).ApplyData.ReceiverRewards.MsgIsZero()) && ((*z).ApplyData.CloseRewards.MsgIsZero()) && ((*z).ApplyData.EvalDelta.MsgIsZero()) && ((*z).ApplyData.ConfigAsset.MsgIsZero()) && ((*z).ApplyData.ApplicationID.MsgIsZero()) } // MarshalMsg implements msgp.Marshaler From a62e8b31fdaa98c3a9c2cdd5a2f641cb37fd5ac7 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Thu, 9 Sep 2021 22:31:57 -0400 Subject: [PATCH 04/28] first round trip with foundation --- data/transactions/logic/README.md | 36 ++++++++-------- data/transactions/logic/TEAL_opcodes.md | 40 +++++++++--------- data/transactions/logic/doc.go | 55 +++++++++++++------------ data/transactions/logic/eval_test.go | 6 +++ 4 files changed, 72 insertions(+), 65 deletions(-) diff --git a/data/transactions/logic/README.md b/data/transactions/logic/README.md index 5aed8c26b7..805d70a20e 100644 --- a/data/transactions/logic/README.md +++ b/data/transactions/logic/README.md @@ -104,10 +104,10 @@ For three-argument ops, `A` is the element two below the top, `B` is the penulti | `keccak256` | Keccak256 hash of value X, yields [32]byte | | `sha512_256` | SHA512_256 hash of value X, yields [32]byte | | `ed25519verify` | for (data A, signature B, pubkey C) verify the signature of ("ProgData" \|\| program_hash \|\| data) against the pubkey => {0 or 1} | -| `+` | A plus B. Panic on overflow. | -| `-` | A minus B. Panic if B > A. | -| `/` | A divided by B (truncated division). Panic if B == 0. | -| `*` | A times B. Panic on overflow. | +| `+` | A plus B. Fail on overflow. | +| `-` | A minus B. Fail if B > A. | +| `/` | A divided by B (truncated division). Fail if B == 0. | +| `*` | A times B. Fail on overflow. | | `<` | A less than B => {0 or 1} | | `>` | A greater than B => {0 or 1} | | `<=` | A less than or equal to B => {0 or 1} | @@ -118,14 +118,14 @@ For three-argument ops, `A` is the element two below the top, `B` is the penulti | `shr` | A divided by 2^B | | `sqrt` | The largest integer B such that B^2 <= X | | `bitlen` | The highest set bit in X. If X is a byte-array, it is interpreted as a big-endian unsigned integer. bitlen of 0 is 0, bitlen of 8 is 4 | -| `exp` | A raised to the Bth power. Panic if A == B == 0 and on overflow | +| `exp` | A raised to the Bth power. Fail if A == B == 0 and on overflow | | `==` | A is equal to B => {0 or 1} | | `!=` | A is not equal to B => {0 or 1} | | `!` | X == 0 yields 1; else 0 | | `len` | yields length of byte value X | | `itob` | converts uint64 X to big endian bytes | | `btoi` | converts bytes X as big endian to uint64 | -| `%` | A modulo B. Panic if B == 0. | +| `%` | A modulo B. Fail if B == 0. | | `\|` | A bitwise-or B | | `&` | A bitwise-and B | | `^` | A bitwise-xor B | @@ -133,7 +133,7 @@ For three-argument ops, `A` is the element two below the top, `B` is the penulti | `mulw` | A times B out to 128-bit long result as low (top) and high uint64 values on the stack | | `addw` | A plus B out to 128-bit long result as sum (top) and carry-bit uint64 values on the stack | | `divmodw` | Pop four uint64 values. The deepest two are interpreted as a uint128 dividend (deepest value is high word), the top two are interpreted as a uint128 divisor. Four uint64 values are pushed to the stack. The deepest two are the quotient (deeper value is the high uint64). The top two are the remainder, low bits on top. | -| `expw` | A raised to the Bth power as a 128-bit long result as low (top) and high uint64 values on the stack. Panic if A == B == 0 or if the results exceeds 2^128-1 | +| `expw` | A raised to the Bth power as a 128-bit long result as low (top) and high uint64 values on the stack. Fail if A == B == 0 or if the results exceeds 2^128-1 | | `getbit` | pop a target A (integer or byte-array), and index B. Push the Bth bit of A. | | `setbit` | pop a target A, index B, and bit C. Set the Bth bit of A to C, and push the result | | `getbyte` | pop a byte-array A and integer B. Extract the Bth byte of A and push it as an integer | @@ -168,8 +168,8 @@ bytes on outputs. | Op | Description | | --- | --- | | `b+` | A plus B, where A and B are byte-arrays interpreted as big-endian unsigned integers | -| `b-` | A minus B, where A and B are byte-arrays interpreted as big-endian unsigned integers. Panic on underflow. | -| `b/` | A divided by B (truncated division), where A and B are byte-arrays interpreted as big-endian unsigned integers. Panic if B is zero. | +| `b-` | A minus B, where A and B are byte-arrays interpreted as big-endian unsigned integers. Fail on underflow. | +| `b/` | A divided by B (truncated division), where A and B are byte-arrays interpreted as big-endian unsigned integers. Fail if B is zero. | | `b*` | A times B, where A and B are byte-arrays interpreted as big-endian unsigned integers. | | `b<` | A is less than B, where A and B are byte-arrays interpreted as big-endian unsigned integers => { 0 or 1} | | `b>` | A is greater than B, where A and B are byte-arrays interpreted as big-endian unsigned integers => { 0 or 1} | @@ -177,7 +177,7 @@ bytes on outputs. | `b>=` | A is greater than or equal to B, where A and B are byte-arrays interpreted as big-endian unsigned integers => { 0 or 1} | | `b==` | A is equals to B, where A and B are byte-arrays interpreted as big-endian unsigned integers => { 0 or 1} | | `b!=` | A is not equal to B, where A and B are byte-arrays interpreted as big-endian unsigned integers => { 0 or 1} | -| `b%` | A modulo B, where A and B are byte-arrays interpreted as big-endian unsigned integers. Panic if B is zero. | +| `b%` | A modulo B, where A and B are byte-arrays interpreted as big-endian unsigned integers. Fail if B is zero. | These opcodes operate on the bits of byte-array values. The shorter array is interpeted as though left padded with zeros until it is the @@ -198,8 +198,8 @@ The following opcodes allow for the construction and submission of | Op | Description | | --- | --- | | `tx_begin` | Begin preparation of a new inner transaction | -| `tx_field f` | Set field F of the current inner transaction to X | -| `tx_submit` | Execute the current inner transaction. Panic on any failure. | +| `tx_field f` | Set field F of the current inner transaction to X. Fail if X is the wrong type for F. | +| `tx_submit` | Execute the current inner transaction. Fail if 16 inner transactions have already been executed, or if the transaction fails | ### Loading Values @@ -230,6 +230,7 @@ Some of these have immediate data in the byte or bytes after the opcode. | `arg_1` | push LogicSig argument 1 to stack | | `arg_2` | push LogicSig argument 2 to stack | | `arg_3` | push LogicSig argument 3 to stack | +| `args` | push Xth LogicSig argument to stack | | `txn f` | push field F of current transaction to stack | | `gtxn t f` | push field F of the Tth transaction in the current group | | `txna f i` | push Ith value of the array field F of the current transaction | @@ -240,15 +241,14 @@ Some of these have immediate data in the byte or bytes after the opcode. | `gtxnsa f i` | push Ith value of the array field F from the Xth transaction in the current group | | `gtxnsas f` | pop an index A and an index B. push Bth value of the array field F from the Ath transaction in the current group | | `global f` | push value from globals to stack | -| `load i` | copy a value from scratch space to the stack | -| `loads` | copy a value from the Xth scratch space to the stack | +| `load i` | copy a value from scratch space to the stack. All scratch spaces are 0 at program start. | +| `loads` | copy a value from the Xth scratch space to the stack. All scratch spaces are 0 at program start. | | `store i` | pop value X. store X to the Ith scratch space | | `stores` | pop indexes A and B. store B to the Ath scratch space | | `gload t i` | push Ith scratch space index of the Tth transaction in the current group | | `gloads i` | push Ith scratch space index of the Xth transaction in the current group | | `gaid t` | push the ID of the asset or application created in the Tth transaction of the current group | | `gaids` | push the ID of the asset or application created in the Xth transaction of the current group | -| `args` | push Xth LogicSig argument to stack | **Transaction Fields** @@ -330,9 +330,9 @@ Global fields are fields that are common to all the transactions in the group. I | 5 | LogicSigVersion | uint64 | Maximum supported TEAL version. LogicSigVersion >= 2. | | 6 | Round | uint64 | Current round number. LogicSigVersion >= 2. | | 7 | LatestTimestamp | uint64 | Last confirmed block UNIX timestamp. Fails if negative. LogicSigVersion >= 2. | -| 8 | CurrentApplicationID | uint64 | ID of current application executing. Fails if no such application is executing. LogicSigVersion >= 2. | +| 8 | CurrentApplicationID | uint64 | ID of current application executing. Fails in LogicSigs. LogicSigVersion >= 2. | | 9 | CreatorAddress | []byte | Address of the creator of the current application. Fails if no such application is executing. LogicSigVersion >= 3. | -| 10 | CurrentApplicationAddress | []byte | Address that the current application controls. Fails if no such application is executing. LogicSigVersion >= 5. | +| 10 | CurrentApplicationAddress | []byte | Address that the current application controls. Fails in LogicSigs. LogicSigVersion >= 5. | | 11 | GroupID | []byte | ID of the transaction group. 32 zero bytes if the transaction is not part of a group. LogicSigVersion >= 5. | @@ -383,7 +383,7 @@ App fields used in the `app_params_get` opcode. | Op | Description | | --- | --- | -| `err` | Error. Panic immediately. This is primarily a fencepost against accidental zero bytes getting compiled into programs. | +| `err` | Error. Fail immediately. This is primarily a fencepost against accidental zero bytes getting compiled into programs. | | `bnz target` | branch to TARGET if value X is not zero | | `bz target` | branch to TARGET if value X is zero | | `b target` | branch unconditionally to TARGET | diff --git a/data/transactions/logic/TEAL_opcodes.md b/data/transactions/logic/TEAL_opcodes.md index c376341bd6..815d075f94 100644 --- a/data/transactions/logic/TEAL_opcodes.md +++ b/data/transactions/logic/TEAL_opcodes.md @@ -8,7 +8,7 @@ Ops have a 'cost' of 1 unless otherwise specified. - Opcode: 0x00 - Pops: _None_ - Pushes: _None_ -- Error. Panic immediately. This is primarily a fencepost against accidental zero bytes getting compiled into programs. +- Error. Fail immediately. This is primarily a fencepost against accidental zero bytes getting compiled into programs. ## sha256 @@ -55,7 +55,7 @@ The 32 byte public key is the last element on the stack, preceded by the 64 byte - Opcode: 0x08 - Pops: *... stack*, {uint64 A}, {uint64 B} - Pushes: uint64 -- A plus B. Panic on overflow. +- A plus B. Fail on overflow. Overflow is an error condition which halts execution and fails the transaction. Full precision is available from `addw`. @@ -64,14 +64,14 @@ Overflow is an error condition which halts execution and fails the transaction. - Opcode: 0x09 - Pops: *... stack*, {uint64 A}, {uint64 B} - Pushes: uint64 -- A minus B. Panic if B > A. +- A minus B. Fail if B > A. ## / - Opcode: 0x0a - Pops: *... stack*, {uint64 A}, {uint64 B} - Pushes: uint64 -- A divided by B (truncated division). Panic if B == 0. +- A divided by B (truncated division). Fail if B == 0. `divmodw` is available to divide the two-element values produced by `mulw` and `addw`. @@ -80,7 +80,7 @@ Overflow is an error condition which halts execution and fails the transaction. - Opcode: 0x0b - Pops: *... stack*, {uint64 A}, {uint64 B} - Pushes: uint64 -- A times B. Panic on overflow. +- A times B. Fail on overflow. Overflow is an error condition which halts execution and fails the transaction. Full precision is available from `mulw`. @@ -168,14 +168,14 @@ Overflow is an error condition which halts execution and fails the transaction. - Pushes: uint64 - converts bytes X as big endian to uint64 -`btoi` panics if the input is longer than 8 bytes. +`btoi` fails if the input is longer than 8 bytes. ## % - Opcode: 0x18 - Pops: *... stack*, {uint64 A}, {uint64 B} - Pushes: uint64 -- A modulo B. Panic if B == 0. +- A modulo B. Fail if B == 0. ## | @@ -462,9 +462,9 @@ FirstValidTime causes the program to fail. The field is reserved for future use. | 5 | LogicSigVersion | uint64 | Maximum supported TEAL version. LogicSigVersion >= 2. | | 6 | Round | uint64 | Current round number. LogicSigVersion >= 2. | | 7 | LatestTimestamp | uint64 | Last confirmed block UNIX timestamp. Fails if negative. LogicSigVersion >= 2. | -| 8 | CurrentApplicationID | uint64 | ID of current application executing. Fails if no such application is executing. LogicSigVersion >= 2. | +| 8 | CurrentApplicationID | uint64 | ID of current application executing. Fails in LogicSigs. LogicSigVersion >= 2. | | 9 | CreatorAddress | []byte | Address of the creator of the current application. Fails if no such application is executing. LogicSigVersion >= 3. | -| 10 | CurrentApplicationAddress | []byte | Address that the current application controls. Fails if no such application is executing. LogicSigVersion >= 5. | +| 10 | CurrentApplicationAddress | []byte | Address that the current application controls. Fails in LogicSigs. LogicSigVersion >= 5. | | 11 | GroupID | []byte | ID of the transaction group. 32 zero bytes if the transaction is not part of a group. LogicSigVersion >= 5. | @@ -482,7 +482,7 @@ for notes on transaction fields available, see `txn`. If this transaction is _i_ - Opcode: 0x34 {uint8 position in scratch space to load from} - Pops: _None_ - Pushes: any -- copy a value from scratch space to the stack +- copy a value from scratch space to the stack. All scratch spaces are 0 at program start. ## store i @@ -574,7 +574,7 @@ for notes on transaction fields available, see `txn`. If top of stack is _i_, `g - Opcode: 0x3e - Pops: *... stack*, uint64 - Pushes: any -- copy a value from the Xth scratch space to the stack +- copy a value from the Xth scratch space to the stack. All scratch spaces are 0 at program start. - LogicSigVersion >= 5 ## stores @@ -702,7 +702,7 @@ See `bnz` for details on how branches work. `b` always jumps to the offset. - pop two byte-arrays A and B and join them, push the result - LogicSigVersion >= 2 -`concat` panics if the result would be greater than 4096 bytes. +`concat` fails if the result would be greater than 4096 bytes. ## substring s e @@ -1073,7 +1073,7 @@ bitlen interprets arrays as big-endian integers, unlike setbit/getbit - Opcode: 0x94 - Pops: *... stack*, {uint64 A}, {uint64 B} - Pushes: uint64 -- A raised to the Bth power. Panic if A == B == 0 and on overflow +- A raised to the Bth power. Fail if A == B == 0 and on overflow - LogicSigVersion >= 4 ## expw @@ -1081,7 +1081,7 @@ bitlen interprets arrays as big-endian integers, unlike setbit/getbit - Opcode: 0x95 - Pops: *... stack*, {uint64 A}, {uint64 B} - Pushes: *... stack*, uint64, uint64 -- A raised to the Bth power as a 128-bit long result as low (top) and high uint64 values on the stack. Panic if A == B == 0 or if the results exceeds 2^128-1 +- A raised to the Bth power as a 128-bit long result as low (top) and high uint64 values on the stack. Fail if A == B == 0 or if the results exceeds 2^128-1 - **Cost**: 10 - LogicSigVersion >= 4 @@ -1099,7 +1099,7 @@ bitlen interprets arrays as big-endian integers, unlike setbit/getbit - Opcode: 0xa1 - Pops: *... stack*, {[]byte A}, {[]byte B} - Pushes: []byte -- A minus B, where A and B are byte-arrays interpreted as big-endian unsigned integers. Panic on underflow. +- A minus B, where A and B are byte-arrays interpreted as big-endian unsigned integers. Fail on underflow. - **Cost**: 10 - LogicSigVersion >= 4 @@ -1108,7 +1108,7 @@ bitlen interprets arrays as big-endian integers, unlike setbit/getbit - Opcode: 0xa2 - Pops: *... stack*, {[]byte A}, {[]byte B} - Pushes: []byte -- A divided by B (truncated division), where A and B are byte-arrays interpreted as big-endian unsigned integers. Panic if B is zero. +- A divided by B (truncated division), where A and B are byte-arrays interpreted as big-endian unsigned integers. Fail if B is zero. - **Cost**: 20 - LogicSigVersion >= 4 @@ -1174,7 +1174,7 @@ bitlen interprets arrays as big-endian integers, unlike setbit/getbit - Opcode: 0xaa - Pops: *... stack*, {[]byte A}, {[]byte B} - Pushes: []byte -- A modulo B, where A and B are byte-arrays interpreted as big-endian unsigned integers. Panic if B is zero. +- A modulo B, where A and B are byte-arrays interpreted as big-endian unsigned integers. Fail if B is zero. - **Cost**: 20 - LogicSigVersion >= 4 @@ -1231,7 +1231,7 @@ bitlen interprets arrays as big-endian integers, unlike setbit/getbit - LogicSigVersion >= 5 - Mode: Application -`log` can be called up to MaxLogCalls times in a program, and log up to a total of 1k bytes. +`log` fails if called more than MaxLogCalls times in a program, or if the sum of logged bytes exceeds 1024 bytes. ## tx_begin @@ -1247,7 +1247,7 @@ bitlen interprets arrays as big-endian integers, unlike setbit/getbit - Opcode: 0xb2 {uint8 transaction field index} - Pops: *... stack*, any - Pushes: _None_ -- Set field F of the current inner transaction to X +- Set field F of the current inner transaction to X. Fail if X is the wrong type for F. - LogicSigVersion >= 5 - Mode: Application @@ -1256,7 +1256,7 @@ bitlen interprets arrays as big-endian integers, unlike setbit/getbit - Opcode: 0xb3 - Pops: _None_ - Pushes: _None_ -- Execute the current inner transaction. Panic on any failure. +- Execute the current inner transaction. Fail if 16 inner transactions have already been executed, or if the transaction fails - LogicSigVersion >= 5 - Mode: Application diff --git a/data/transactions/logic/doc.go b/data/transactions/logic/doc.go index d714849d25..55723ded44 100644 --- a/data/transactions/logic/doc.go +++ b/data/transactions/logic/doc.go @@ -24,15 +24,15 @@ import ( // short description of every op var opDocByName = map[string]string{ - "err": "Error. Panic immediately. This is primarily a fencepost against accidental zero bytes getting compiled into programs.", + "err": "Error. Fail immediately. This is primarily a fencepost against accidental zero bytes getting compiled into programs.", "sha256": "SHA256 hash of value X, yields [32]byte", "keccak256": "Keccak256 hash of value X, yields [32]byte", "sha512_256": "SHA512_256 hash of value X, yields [32]byte", "ed25519verify": "for (data A, signature B, pubkey C) verify the signature of (\"ProgData\" || program_hash || data) against the pubkey => {0 or 1}", - "+": "A plus B. Panic on overflow.", - "-": "A minus B. Panic if B > A.", - "/": "A divided by B (truncated division). Panic if B == 0.", - "*": "A times B. Panic on overflow.", + "+": "A plus B. Fail on overflow.", + "-": "A minus B. Fail if B > A.", + "/": "A divided by B (truncated division). Fail if B == 0.", + "*": "A times B. Fail on overflow.", "<": "A less than B => {0 or 1}", ">": "A greater than B => {0 or 1}", "<=": "A less than or equal to B => {0 or 1}", @@ -45,7 +45,7 @@ var opDocByName = map[string]string{ "len": "yields length of byte value X", "itob": "converts uint64 X to big endian bytes", "btoi": "converts bytes X as big endian to uint64", - "%": "A modulo B. Panic if B == 0.", + "%": "A modulo B. Fail if B == 0.", "|": "A bitwise-or B", "&": "A bitwise-and B", "^": "A bitwise-xor B", @@ -54,8 +54,8 @@ var opDocByName = map[string]string{ "shr": "A divided by 2^B", "sqrt": "The largest integer B such that B^2 <= X", "bitlen": "The highest set bit in X. If X is a byte-array, it is interpreted as a big-endian unsigned integer. bitlen of 0 is 0, bitlen of 8 is 4", - "exp": "A raised to the Bth power. Panic if A == B == 0 and on overflow", - "expw": "A raised to the Bth power as a 128-bit long result as low (top) and high uint64 values on the stack. Panic if A == B == 0 or if the results exceeds 2^128-1", + "exp": "A raised to the Bth power. Fail if A == B == 0 and on overflow", + "expw": "A raised to the Bth power as a 128-bit long result as low (top) and high uint64 values on the stack. Fail if A == B == 0 or if the results exceeds 2^128-1", "mulw": "A times B out to 128-bit long result as low (top) and high uint64 values on the stack", "addw": "A plus B out to 128-bit long result as sum (top) and carry-bit uint64 values on the stack", "divmodw": "Pop four uint64 values. The deepest two are interpreted as a uint128 dividend (deepest value is high word), the top two are interpreted as a uint128 divisor. Four uint64 values are pushed to the stack. The deepest two are the quotient (deeper value is the high uint64). The top two are the remainder, low bits on top.", @@ -79,16 +79,20 @@ var opDocByName = map[string]string{ "arg_1": "push LogicSig argument 1 to stack", "arg_2": "push LogicSig argument 2 to stack", "arg_3": "push LogicSig argument 3 to stack", + "args": "push Xth LogicSig argument to stack", "txn": "push field F of current transaction to stack", "gtxn": "push field F of the Tth transaction in the current group", "gtxns": "push field F of the Xth transaction in the current group", "txna": "push Ith value of the array field F of the current transaction", "gtxna": "push Ith value of the array field F from the Tth transaction in the current group", "gtxnsa": "push Ith value of the array field F from the Xth transaction in the current group", + "txnas": "push Xth value of the array field F of the current transaction", + "gtxnas": "push Xth value of the array field F from the Tth transaction in the current group", + "gtxnsas": "pop an index A and an index B. push Bth value of the array field F from the Ath transaction in the current group", "global": "push value from globals to stack", - "load": "copy a value from scratch space to the stack", + "load": "copy a value from scratch space to the stack. All scratch spaces are 0 at program start.", "store": "pop value X. store X to the Ith scratch space", - "loads": "copy a value from the Xth scratch space to the stack", + "loads": "copy a value from the Xth scratch space to the stack. All scratch spaces are 0 at program start.", "stores": "pop indexes A and B. store B to the Ath scratch space", "gload": "push Ith scratch space index of the Tth transaction in the current group", "gloads": "push Ith scratch space index of the Xth transaction in the current group", @@ -138,8 +142,8 @@ var opDocByName = map[string]string{ "retsub": "pop the top instruction from the call stack and branch to it", "b+": "A plus B, where A and B are byte-arrays interpreted as big-endian unsigned integers", - "b-": "A minus B, where A and B are byte-arrays interpreted as big-endian unsigned integers. Panic on underflow.", - "b/": "A divided by B (truncated division), where A and B are byte-arrays interpreted as big-endian unsigned integers. Panic if B is zero.", + "b-": "A minus B, where A and B are byte-arrays interpreted as big-endian unsigned integers. Fail on underflow.", + "b/": "A divided by B (truncated division), where A and B are byte-arrays interpreted as big-endian unsigned integers. Fail if B is zero.", "b*": "A times B, where A and B are byte-arrays interpreted as big-endian unsigned integers.", "b<": "A is less than B, where A and B are byte-arrays interpreted as big-endian unsigned integers => { 0 or 1}", "b>": "A is greater than B, where A and B are byte-arrays interpreted as big-endian unsigned integers => { 0 or 1}", @@ -147,7 +151,7 @@ var opDocByName = map[string]string{ "b>=": "A is greater than or equal to B, where A and B are byte-arrays interpreted as big-endian unsigned integers => { 0 or 1}", "b==": "A is equals to B, where A and B are byte-arrays interpreted as big-endian unsigned integers => { 0 or 1}", "b!=": "A is not equal to B, where A and B are byte-arrays interpreted as big-endian unsigned integers => { 0 or 1}", - "b%": "A modulo B, where A and B are byte-arrays interpreted as big-endian unsigned integers. Panic if B is zero.", + "b%": "A modulo B, where A and B are byte-arrays interpreted as big-endian unsigned integers. Fail if B is zero.", "b|": "A bitwise-or B, where A and B are byte-arrays, zero-left extended to the greater of their lengths", "b&": "A bitwise-and B, where A and B are byte-arrays, zero-left extended to the greater of their lengths", "b^": "A bitwise-xor B, where A and B are byte-arrays, zero-left extended to the greater of their lengths", @@ -155,13 +159,8 @@ var opDocByName = map[string]string{ "log": "write bytes to log state of the current application", "tx_begin": "Begin preparation of a new inner transaction", - "tx_field": "Set field F of the current inner transaction to X", - "tx_submit": "Execute the current inner transaction. Panic on any failure.", - - "txnas": "push Xth value of the array field F of the current transaction", - "gtxnas": "push Xth value of the array field F from the Tth transaction in the current group", - "gtxnsas": "pop an index A and an index B. push Bth value of the array field F from the Ath transaction in the current group", - "args": "push Xth LogicSig argument to stack", + "tx_field": "Set field F of the current inner transaction to X. Fail if X is the wrong type for F.", + "tx_submit": "Execute the current inner transaction. Fail if 16 inner transactions have already been executed, or if the transaction fails", } // OpDoc returns a description of the op @@ -233,8 +232,8 @@ var opDocExtras = map[string]string{ "gloads": "`gloads` fails unless the requested transaction is an ApplicationCall and X < GroupIndex.", "gaid": "`gaid` fails unless the requested transaction created an asset or application and T < GroupIndex.", "gaids": "`gaids` fails unless the requested transaction created an asset or application and X < GroupIndex.", - "btoi": "`btoi` panics if the input is longer than 8 bytes.", - "concat": "`concat` panics if the result would be greater than 4096 bytes.", + "btoi": "`btoi` fails if the input is longer than 8 bytes.", + "concat": "`concat` fails if the result would be greater than 4096 bytes.", "pushbytes": "pushbytes args are not added to the bytecblock during assembly processes", "pushint": "pushint args are not added to the intcblock during assembly processes", "getbit": "see explanation of bit ordering in setbit", @@ -252,7 +251,7 @@ var opDocExtras = map[string]string{ "asset_holding_get": "params: Txn.Accounts offset (or, since v4, an account address that appears in Txn.Accounts or is Txn.Sender), asset id (or, since v4, a Txn.ForeignAssets offset). Return: did_exist flag (1 if exist and 0 otherwise), value.", "asset_params_get": "params: Before v4, Txn.ForeignAssets offset. Since v4, Txn.ForeignAssets offset or an asset id that appears in Txn.ForeignAssets. Return: did_exist flag (1 if exist and 0 otherwise), value.", "app_params_get": "params: Txn.ForeignApps offset or an app id that appears in Txn.ForeignApps. Return: did_exist flag (1 if exist and 0 otherwise), value.", - "log": "`log` can be called up to MaxLogCalls times in a program, and log up to a total of 1k bytes.", + "log": "`log` fails if called more than MaxLogCalls times in a program, or if the sum of logged bytes exceeds 1024 bytes.", } // OpDocExtra returns extra documentation text about an op @@ -260,13 +259,15 @@ func OpDocExtra(opName string) string { return opDocExtras[opName] } -// OpGroups is groupings of ops for documentation purposes. +// OpGroups is groupings of ops for documentation purposes. The order +// here is the order args opcodes are presented, so place related +// opcodes consecutively, even if their opcode values are not. var OpGroups = map[string][]string{ "Arithmetic": {"sha256", "keccak256", "sha512_256", "ed25519verify", "+", "-", "/", "*", "<", ">", "<=", ">=", "&&", "||", "shl", "shr", "sqrt", "bitlen", "exp", "==", "!=", "!", "len", "itob", "btoi", "%", "|", "&", "^", "~", "mulw", "addw", "divmodw", "expw", "getbit", "setbit", "getbyte", "setbyte", "concat"}, "Byte Array Slicing": {"substring", "substring3", "extract", "extract3", "extract16bits", "extract32bits", "extract64bits"}, "Byte Array Arithmetic": {"b+", "b-", "b/", "b*", "b<", "b>", "b<=", "b>=", "b==", "b!=", "b%"}, "Byte Array Logic": {"b|", "b&", "b^", "b~"}, - "Loading Values": {"intcblock", "intc", "intc_0", "intc_1", "intc_2", "intc_3", "pushint", "bytecblock", "bytec", "bytec_0", "bytec_1", "bytec_2", "bytec_3", "pushbytes", "bzero", "arg", "arg_0", "arg_1", "arg_2", "arg_3", "txn", "gtxn", "txna", "txnas", "gtxna", "gtxnas", "gtxns", "gtxnsa", "gtxnsas", "global", "load", "loads", "store", "stores", "gload", "gloads", "gaid", "gaids", "args"}, + "Loading Values": {"intcblock", "intc", "intc_0", "intc_1", "intc_2", "intc_3", "pushint", "bytecblock", "bytec", "bytec_0", "bytec_1", "bytec_2", "bytec_3", "pushbytes", "bzero", "arg", "arg_0", "arg_1", "arg_2", "arg_3", "args", "txn", "gtxn", "txna", "txnas", "gtxna", "gtxnas", "gtxns", "gtxnsa", "gtxnsas", "global", "load", "loads", "store", "stores", "gload", "gloads", "gaid", "gaids"}, "Flow Control": {"err", "bnz", "bz", "b", "return", "pop", "dup", "dup2", "dig", "cover", "uncover", "swap", "select", "assert", "callsub", "retsub"}, "State Access": {"balance", "min_balance", "app_opted_in", "app_local_get", "app_local_get_ex", "app_global_get", "app_global_get_ex", "app_local_put", "app_global_put", "app_local_del", "app_global_del", "asset_holding_get", "asset_params_get", "app_params_get", "log"}, "Inner Transactions": {"tx_begin", "tx_field", "tx_submit"}, @@ -411,9 +412,9 @@ var globalFieldDocs = map[string]string{ "LogicSigVersion": "Maximum supported TEAL version", "Round": "Current round number", "LatestTimestamp": "Last confirmed block UNIX timestamp. Fails if negative", - "CurrentApplicationID": "ID of current application executing. Fails if no such application is executing", + "CurrentApplicationID": "ID of current application executing. Fails in LogicSigs", "CreatorAddress": "Address of the creator of the current application. Fails if no such application is executing", - "CurrentApplicationAddress": "Address that the current application controls. Fails if no such application is executing", + "CurrentApplicationAddress": "Address that the current application controls. Fails in LogicSigs", "GroupID": "ID of the transaction group. 32 zero bytes if the transaction is not part of a group.", } diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index 1e507d75cb..1dd6d8e591 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -2264,6 +2264,9 @@ func TestExtractOp(t *testing.T) { testAccepts(t, "byte 0x123456789abcdef0; int 1; extract32bits; int 0x3456789a; ==", 5) testAccepts(t, "byte 0x123456789abcdef0; int 0; extract64bits; int 0x123456789abcdef0; ==", 5) testAccepts(t, "byte 0x123456789abcdef0; int 0; extract64bits; int 0x123456789abcdef; !=", 5) + + testAccepts(t, `byte "hello"; extract 5 0; byte ""; ==`, 5) + testAccepts(t, `byte "hello"; int 5; int 0; extract3; byte ""; ==`, 5) } func TestExtractFlop(t *testing.T) { @@ -2323,6 +2326,8 @@ func TestLoadStore(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() + testAccepts(t, "load 3; int 0; ==;", 1) + testAccepts(t, `int 37 int 37 store 1 @@ -2344,6 +2349,7 @@ func TestLoadStoreStack(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() + testAccepts(t, "itn 3; loads; int 0; ==;", 1) testAccepts(t, `int 37 int 1 int 37 From c728fd6a4ccd766c8a96c2ef4321506123b4fe83 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Fri, 10 Sep 2021 09:09:39 -0400 Subject: [PATCH 05/28] Adjust v1 REST API to get app/asa id from ApplyData if possible --- .../algod/api/server/v1/handlers/handlers.go | 22 +++++++++++-- daemon/algod/api/server/v2/utils.go | 4 +-- ledger/apptxn_test.go | 33 +++++++++++-------- 3 files changed, 42 insertions(+), 17 deletions(-) diff --git a/daemon/algod/api/server/v1/handlers/handlers.go b/daemon/algod/api/server/v1/handlers/handlers.go index f956fb4174..5c560d93a2 100644 --- a/daemon/algod/api/server/v1/handlers/handlers.go +++ b/daemon/algod/api/server/v1/handlers/handlers.go @@ -395,7 +395,7 @@ func computeCreatableIndexInPayset(tx node.TxnWithStatus, txnCounter uint64, pay // computeAssetIndexFromTxn returns the created asset index given a confirmed // transaction whose confirmation block is available in the ledger. Note that // 0 is an invalid asset index (they start at 1). -func computeAssetIndexFromTxn(tx node.TxnWithStatus, l *data.Ledger) (aidx uint64) { +func computeAssetIndexFromTxn(tx node.TxnWithStatus, l *data.Ledger) uint64 { // Must have ledger if l == nil { return 0 @@ -413,6 +413,15 @@ func computeAssetIndexFromTxn(tx node.TxnWithStatus, l *data.Ledger) (aidx uint6 return 0 } + aidx := uint64(tx.ApplyData.ConfigAsset) + if aidx > 0 { + return aidx + } + // If there is no ConfigAsset in the ApplyData, it must be a + // transaction before inner transactions were activated. Therefore + // the computeCreatableIndexInPayset function will work properly + // to deduce the aid. Proceed. + // Look up block where transaction was confirmed blk, err := l.Block(tx.ConfirmedRound) if err != nil { @@ -430,7 +439,7 @@ func computeAssetIndexFromTxn(tx node.TxnWithStatus, l *data.Ledger) (aidx uint6 // computeAppIndexFromTxn returns the created app index given a confirmed // transaction whose confirmation block is available in the ledger. Note that // 0 is an invalid asset index (they start at 1). -func computeAppIndexFromTxn(tx node.TxnWithStatus, l *data.Ledger) (aidx uint64) { +func computeAppIndexFromTxn(tx node.TxnWithStatus, l *data.Ledger) uint64 { // Must have ledger if l == nil { return 0 @@ -448,6 +457,15 @@ func computeAppIndexFromTxn(tx node.TxnWithStatus, l *data.Ledger) (aidx uint64) return 0 } + aidx := uint64(tx.ApplyData.ApplicationID) + if aidx > 0 { + return aidx + } + // If there is no ConfigAsset in the ApplyData, it must be a + // transaction before inner transactions were activated. Therefore + // the computeCreatableIndexInPayset function will work properly + // to deduce the aidx. Proceed. + // Look up block where transaction was confirmed blk, err := l.Block(tx.ConfirmedRound) if err != nil { diff --git a/daemon/algod/api/server/v2/utils.go b/daemon/algod/api/server/v2/utils.go index 8074370a2e..67c36b61f6 100644 --- a/daemon/algod/api/server/v2/utils.go +++ b/daemon/algod/api/server/v2/utils.go @@ -110,7 +110,7 @@ func computeCreatableIndexInPayset(tx node.TxnWithStatus, txnCounter uint64, pay // computeAssetIndexFromTxn returns the created asset index given a confirmed // transaction whose confirmation block is available in the ledger. Note that // 0 is an invalid asset index (they start at 1). -func computeAssetIndexFromTxn(tx node.TxnWithStatus, l *data.Ledger) (aidx *uint64) { +func computeAssetIndexFromTxn(tx node.TxnWithStatus, l *data.Ledger) *uint64 { // Must have ledger if l == nil { return nil @@ -154,7 +154,7 @@ func computeAssetIndexFromTxn(tx node.TxnWithStatus, l *data.Ledger) (aidx *uint // computeAppIndexFromTxn returns the created app index given a confirmed // transaction whose confirmation block is available in the ledger. Note that // 0 is an invalid asset index (they start at 1). -func computeAppIndexFromTxn(tx node.TxnWithStatus, l *data.Ledger) (aidx *uint64) { +func computeAppIndexFromTxn(tx node.TxnWithStatus, l *data.Ledger) *uint64 { // Must have ledger if l == nil { return nil diff --git a/ledger/apptxn_test.go b/ledger/apptxn_test.go index 990f73474b..0637c7e4ce 100644 --- a/ledger/apptxn_test.go +++ b/ledger/apptxn_test.go @@ -60,27 +60,31 @@ func TestPayAction(t *testing.T) { `), } + ai := basics.AppIndex(1) fund := txntest.Txn{ Type: "pay", Sender: addrs[0], - Receiver: basics.AppIndex(1).Address(), + Receiver: ai.Address(), Amount: 200000, // account min balance, plus fees } payout1 := txntest.Txn{ Type: "appl", Sender: addrs[1], - ApplicationID: basics.AppIndex(1), + ApplicationID: ai, Accounts: []basics.Address{addrs[1]}, // pay self } eval := l.nextBlock(t) eval.txns(t, &create, &fund, &payout1) - l.endBlock(t, eval) + vb := l.endBlock(t, eval) + + // AD contains expected appIndex + require.Equal(t, ai, vb.blk.Payset[0].ApplyData.ApplicationID) ad0 := l.micros(t, addrs[0]) ad1 := l.micros(t, addrs[1]) - app := l.micros(t, basics.AppIndex(1).Address()) + app := l.micros(t, ai.Address()) // create(1000) and fund(1000 + 200000) require.Equal(t, uint64(202000), genBalances.Balances[addrs[0]].MicroAlgos.Raw-ad0) @@ -99,7 +103,7 @@ func TestPayAction(t *testing.T) { payout2 := txntest.Txn{ Type: "appl", Sender: addrs[1], - ApplicationID: basics.AppIndex(1), + ApplicationID: ai, Accounts: []basics.Address{addrs[2]}, // pay other } eval.txn(t, &payout2) @@ -128,7 +132,7 @@ func TestPayAction(t *testing.T) { ad1 = l.micros(t, addrs[1]) ad2 := l.micros(t, addrs[2]) - app = l.micros(t, basics.AppIndex(1).Address()) + app = l.micros(t, ai.Address()) // paid 5000, in first payout (only), but paid 1000 fee in each payout txn require.Equal(t, rewards+3000, ad1-genBalances.Balances[addrs[1]].MicroAlgos.Raw) @@ -143,13 +147,13 @@ func TestPayAction(t *testing.T) { tenkalgos := txntest.Txn{ Type: "pay", Sender: addrs[0], - Receiver: basics.AppIndex(1).Address(), + Receiver: ai.Address(), Amount: 10 * 1000 * 1000000, // account min balance, plus fees } eval = l.nextBlock(t) eval.txn(t, &tenkalgos) l.endBlock(t, eval) - beforepay := l.micros(t, basics.AppIndex(1).Address()) + beforepay := l.micros(t, ai.Address()) // Build up Residue in RewardsState so it's ready to pay again for i := 1; i < 10; i++ { @@ -160,7 +164,7 @@ func TestPayAction(t *testing.T) { eval.txn(t, payout2.Noted("2")) l.endBlock(t, eval) - afterpay := l.micros(t, basics.AppIndex(1).Address()) + afterpay := l.micros(t, ai.Address()) payInBlock = eval.block.Payset[0] inners = payInBlock.ApplyData.EvalDelta.InnerTxns @@ -229,11 +233,12 @@ submit: tx_submit eval := l.nextBlock(t) eval.txns(t, &asa, &app) - l.endBlock(t, eval) + vb := l.endBlock(t, eval) - // Would be better to pull these out of block, or at least check them. asaIndex := basics.AssetIndex(1) + require.Equal(t, asaIndex, vb.blk.Payset[0].ApplyData.ConfigAsset) appIndex := basics.AppIndex(2) + require.Equal(t, appIndex, vb.blk.Payset[1].ApplyData.ApplicationID) fund := txntest.Txn{ Type: "pay", @@ -372,7 +377,6 @@ func TestClawbackAction(t *testing.T) { l := newTestLedger(t, genBalances) defer l.Close() - // Would be better to pull these out of block, or at least check them. asaIndex := basics.AssetIndex(1) appIndex := basics.AppIndex(2) @@ -422,7 +426,10 @@ func TestClawbackAction(t *testing.T) { } eval := l.nextBlock(t) eval.txns(t, &asa, &app, &optin) - l.endBlock(t, eval) + vb := l.endBlock(t, eval) + + require.Equal(t, asaIndex, vb.blk.Payset[0].ApplyData.ConfigAsset) + require.Equal(t, appIndex, vb.blk.Payset[1].ApplyData.ApplicationID) bystander := addrs[2] // Has no authority of its own overpay := txntest.Txn{ From 27b53c172fba860011c3afa608c522722948f50c Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Fri, 10 Sep 2021 14:38:03 -0400 Subject: [PATCH 06/28] Typo Co-authored-by: Jason Paulos --- daemon/algod/api/server/v1/handlers/handlers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/algod/api/server/v1/handlers/handlers.go b/daemon/algod/api/server/v1/handlers/handlers.go index 5c560d93a2..0384e887e8 100644 --- a/daemon/algod/api/server/v1/handlers/handlers.go +++ b/daemon/algod/api/server/v1/handlers/handlers.go @@ -461,7 +461,7 @@ func computeAppIndexFromTxn(tx node.TxnWithStatus, l *data.Ledger) uint64 { if aidx > 0 { return aidx } - // If there is no ConfigAsset in the ApplyData, it must be a + // If there is no ApplicationID in the ApplyData, it must be a // transaction before inner transactions were activated. Therefore // the computeCreatableIndexInPayset function will work properly // to deduce the aidx. Proceed. From 96b4b7fa1947188b3de616ca15c34f01aeb62565 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Fri, 10 Sep 2021 14:40:38 -0400 Subject: [PATCH 07/28] Temporary explanation in leiu of a new table of fields. --- data/transactions/logic/TEAL_opcodes.md | 4 ++++ data/transactions/logic/doc.go | 2 ++ 2 files changed, 6 insertions(+) diff --git a/data/transactions/logic/TEAL_opcodes.md b/data/transactions/logic/TEAL_opcodes.md index 815d075f94..cb613d93c4 100644 --- a/data/transactions/logic/TEAL_opcodes.md +++ b/data/transactions/logic/TEAL_opcodes.md @@ -1242,6 +1242,8 @@ bitlen interprets arrays as big-endian integers, unlike setbit/getbit - LogicSigVersion >= 5 - Mode: Application +`tx_begin` sets Sender to the application address; Fee to the minimum allowable, taking into account MinTxnFee and credit from overpaying in earlier transactions; and FirstValid/LastValid to the values in the top-level transaction. + ## tx_field f - Opcode: 0xb2 {uint8 transaction field index} @@ -1251,6 +1253,8 @@ bitlen interprets arrays as big-endian integers, unlike setbit/getbit - LogicSigVersion >= 5 - Mode: Application +The following fields may be set by `tx_field` - Sender, Fee, Receiver, Amount, CloseRemainderTo, Type, TypeEnum, XferAsset, AssetAmount, AssetSender, AssetReceiver, AssetCloseTo + ## tx_submit - Opcode: 0xb3 diff --git a/data/transactions/logic/doc.go b/data/transactions/logic/doc.go index 55723ded44..8c511515b5 100644 --- a/data/transactions/logic/doc.go +++ b/data/transactions/logic/doc.go @@ -252,6 +252,8 @@ var opDocExtras = map[string]string{ "asset_params_get": "params: Before v4, Txn.ForeignAssets offset. Since v4, Txn.ForeignAssets offset or an asset id that appears in Txn.ForeignAssets. Return: did_exist flag (1 if exist and 0 otherwise), value.", "app_params_get": "params: Txn.ForeignApps offset or an app id that appears in Txn.ForeignApps. Return: did_exist flag (1 if exist and 0 otherwise), value.", "log": "`log` fails if called more than MaxLogCalls times in a program, or if the sum of logged bytes exceeds 1024 bytes.", + "tx_begin": "`tx_begin` sets Sender to the application address; Fee to the minimum allowable, taking into account MinTxnFee and credit from overpaying in earlier transactions; and FirstValid/LastValid to the values in the top-level transaction.", + "tx_field": "The following fields may be set by `tx_field` - Sender, Fee, Receiver, Amount, CloseRemainderTo, Type, TypeEnum, XferAsset, AssetAmount, AssetSender, AssetReceiver, AssetCloseTo", } // OpDocExtra returns extra documentation text about an op From d575c64f4b9e4aaa349ebf413fd92d991f92fd6d Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Fri, 10 Sep 2021 15:02:45 -0400 Subject: [PATCH 08/28] Confirm followup creates have the expected id after inners --- ledger/apptxn_test.go | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/ledger/apptxn_test.go b/ledger/apptxn_test.go index 0637c7e4ce..005916fe62 100644 --- a/ledger/apptxn_test.go +++ b/ledger/apptxn_test.go @@ -640,6 +640,7 @@ func TestDuplicatePayAction(t *testing.T) { l := newTestLedger(t, genBalances) defer l.Close() + appIndex := basics.AppIndex(1) create := txntest.Txn{ Type: "appl", Sender: addrs[0], @@ -666,31 +667,44 @@ func TestDuplicatePayAction(t *testing.T) { fund := txntest.Txn{ Type: "pay", Sender: addrs[0], - Receiver: basics.AppIndex(1).Address(), + Receiver: appIndex.Address(), Amount: 200000, // account min balance, plus fees } paytwice := txntest.Txn{ Type: "appl", Sender: addrs[1], - ApplicationID: basics.AppIndex(1), + ApplicationID: appIndex, Accounts: []basics.Address{addrs[1]}, // pay self } eval := l.nextBlock(t) - eval.txns(t, &create, &fund, &paytwice) - l.endBlock(t, eval) + eval.txns(t, &create, &fund, &paytwice, create.Noted("in same block")) + vb := l.endBlock(t, eval) + + require.Equal(t, appIndex, vb.blk.Payset[0].ApplyData.ApplicationID) + require.Equal(t, 4, len(vb.blk.Payset)) + // create=1, fund=2, payTwice=3,4,5 + require.Equal(t, basics.AppIndex(6), vb.blk.Payset[3].ApplyData.ApplicationID) ad0 := l.micros(t, addrs[0]) ad1 := l.micros(t, addrs[1]) - app := l.micros(t, basics.AppIndex(1).Address()) + app := l.micros(t, appIndex.Address()) - // create(1000) and fund(1000 + 200000) - require.Equal(t, 202000, int(genBalances.Balances[addrs[0]].MicroAlgos.Raw-ad0)) + // create(1000) and fund(1000 + 200000), extra create (1000) + require.Equal(t, 203000, int(genBalances.Balances[addrs[0]].MicroAlgos.Raw-ad0)) // paid 10000, but 1000 fee on tx require.Equal(t, 9000, int(ad1-genBalances.Balances[addrs[1]].MicroAlgos.Raw)) // app still has 188000 (paid out 10000, and paid 2 x fee to do it) require.Equal(t, 188000, int(app)) + + // Now create another app, and see if it gets the index we expect. + eval = l.nextBlock(t) + eval.txns(t, create.Noted("again")) + vb = l.endBlock(t, eval) + + // create=1, fund=2, payTwice=3,4,5, insameblock=6 + require.Equal(t, basics.AppIndex(7), vb.blk.Payset[0].ApplyData.ApplicationID) } // TestInnerTxCount ensures that inner transactions increment the TxnCounter From 9b7bcc2784902765ccddf44a48352775a63dd9bd Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Fri, 10 Sep 2021 15:27:01 -0400 Subject: [PATCH 09/28] Test suggested by @jasonpaulos --- ledger/eval.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ledger/eval.go b/ledger/eval.go index 53ec72a050..acd5f62204 100644 --- a/ledger/eval.go +++ b/ledger/eval.go @@ -904,6 +904,13 @@ func (eval *BlockEvaluator) transaction(txn transactions.SignedTxn, evalParams * } } + // We are not allowing InnerTxns to have InnerTxns yet. Error if that happens. + for _, itx := range applyData.EvalDelta.InnerTxns { + if len(itx.ApplyData.EvalDelta.InnerTxns) > 0 { + return fmt.Errorf("inner transaction has inner transactions %v", itx) + } + } + // Remember this txn cow.addTx(txn.Txn, txid, uint64(len(applyData.EvalDelta.InnerTxns))) // Will need to be recursive when inners are From bae285953b9b46ecfb0f9477b13e6373c342d5de Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Fri, 10 Sep 2021 22:15:45 -0400 Subject: [PATCH 10/28] acfg and afrz inner transactions This also sorts out inner txcounting, so that an innner transaction knows the current txncount as it executes, so it can get the right id if it creates. --- data/pools/transactionPool.go | 2 +- data/transactions/logic/eval.go | 82 ++++++++++-- data/transactions/logic/evalAppTxn_test.go | 77 +++++++++++- data/transactions/logic/eval_test.go | 12 +- data/transactions/logic/fields.go | 32 ++--- data/transactions/logictest/ledger.go | 55 ++++++++ ledger/applications.go | 17 +++ ledger/applications_test.go | 9 ++ ledger/apptxn_test.go | 140 ++++++++++++++++++++- ledger/cow.go | 8 +- ledger/eval.go | 6 +- 11 files changed, 403 insertions(+), 37 deletions(-) diff --git a/data/pools/transactionPool.go b/data/pools/transactionPool.go index 3d7125f5e0..35871e91ab 100644 --- a/data/pools/transactionPool.go +++ b/data/pools/transactionPool.go @@ -539,7 +539,7 @@ func (pool *TransactionPool) isAssemblyTimedOut() bool { // we have no deadline, so no reason to timeout. return false } - generateBlockDuration := generateBlockBaseDuration + time.Duration(pool.pendingBlockEvaluator.TxnCounter())*generateBlockTransactionDuration + generateBlockDuration := generateBlockBaseDuration + time.Duration(pool.pendingBlockEvaluator.PaySetSize())*generateBlockTransactionDuration return time.Now().After(pool.assemblyDeadline.Add(-generateBlockDuration)) } diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 8a44143340..332069c596 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -114,6 +114,30 @@ func (sv *stackValue) uint() (uint64, error) { return sv.Uint, nil } +func (sv *stackValue) bool() (bool, error) { + u64, err := sv.uint() + if err != nil { + return false, err + } + if u64 == 1 { + return true, nil + } + if u64 == 0 { + return false, nil + } + return false, fmt.Errorf("boolean is neither 1 nor 0: %d", u64) +} + +func (sv *stackValue) string(limit int) (string, error) { + if sv.Bytes == nil { + return "", errors.New("not a byte array") + } + if len(sv.Bytes) > limit { + return "", errors.New("value is too long") + } + return string(sv.Bytes), nil +} + func stackValueFromTealValue(tv *basics.TealValue) (sv stackValue, err error) { switch tv.Type { case basics.TealBytesType: @@ -3506,12 +3530,14 @@ func (cx *EvalContext) stackIntoTxnField(sv stackValue, fs txnFieldSpec, txn *tr // KeyReg not allowed yet, so no fields settable + // Payment case Receiver: txn.Receiver, err = cx.availableAccount(sv) case Amount: txn.Amount.Raw, err = sv.uint() case CloseRemainderTo: txn.CloseRemainderTo, err = cx.availableAccount(sv) + // AssetTransfer case XferAsset: txn.XferAsset, err = cx.availableAsset(sv) case AssetAmount: @@ -3522,10 +3548,52 @@ func (cx *EvalContext) stackIntoTxnField(sv stackValue, fs txnFieldSpec, txn *tr txn.AssetReceiver, err = cx.availableAccount(sv) case AssetCloseTo: txn.AssetCloseTo, err = cx.availableAccount(sv) - - // acfg likely next - - // afrz seems easy but not high demand + // AssetConfig + case ConfigAsset: + txn.ConfigAsset, err = cx.availableAsset(sv) + case ConfigAssetTotal: + txn.AssetParams.Total, err = sv.uint() + case ConfigAssetDecimals: + var decimals uint64 + decimals, err = sv.uint() + if err == nil { + if decimals > uint64(cx.Proto.MaxAssetDecimals) { + err = fmt.Errorf("too many decimals") + } else { + txn.AssetParams.Decimals = uint32(decimals) + } + } + case ConfigAssetDefaultFrozen: + txn.AssetParams.DefaultFrozen, err = sv.bool() + case ConfigAssetUnitName: + txn.AssetParams.UnitName, err = sv.string(cx.Proto.MaxAssetUnitNameBytes) + case ConfigAssetName: + txn.AssetParams.AssetName, err = sv.string(cx.Proto.MaxAssetNameBytes) + case ConfigAssetURL: + txn.AssetParams.URL, err = sv.string(cx.Proto.MaxAssetURLBytes) + case ConfigAssetMetadataHash: + if sv.Bytes == nil { + err = fmt.Errorf("ConfigAssetMetadataHash must be bytes") + } else if len(sv.Bytes) > 32 { + err = fmt.Errorf("ConfigAssetMetadataHash must be <= 32 bytes") + } else { + copy(txn.AssetParams.MetadataHash[:], sv.Bytes) + } + case ConfigAssetManager: + txn.AssetParams.Manager, err = sv.address() + case ConfigAssetReserve: + txn.AssetParams.Reserve, err = sv.address() + case ConfigAssetFreeze: + txn.AssetParams.Freeze, err = sv.address() + case ConfigAssetClawback: + txn.AssetParams.Clawback, err = sv.address() + // Freeze + case FreezeAsset: + txn.FreezeAsset, err = cx.availableAsset(sv) + case FreezeAssetAccount: + txn.FreezeAccount, err = cx.availableAccount(sv) + case FreezeAssetFrozen: + txn.AssetFrozen, err = sv.bool() // appl needs to wait. Can't call AVM from AVM. @@ -3569,10 +3637,10 @@ func opTxSubmit(cx *EvalContext) { // Error out on anything unusual. Allow pay, axfer. switch cx.subtxn.Txn.Type { - case protocol.PaymentTx, protocol.AssetTransferTx: - // only pay and axfer for now + case protocol.PaymentTx, protocol.AssetTransferTx, protocol.AssetConfigTx, protocol.AssetFreezeTx: + // only pay, axfer, acfg, afrz for now default: - cx.err = fmt.Errorf("Invalid inner transaction type %s", cx.subtxn.Txn.Type) + cx.err = fmt.Errorf("Invalid inner transaction type %#v", cx.subtxn.Txn.Type) return } diff --git a/data/transactions/logic/evalAppTxn_test.go b/data/transactions/logic/evalAppTxn_test.go index bac19afd7f..a48ce5ca29 100644 --- a/data/transactions/logic/evalAppTxn_test.go +++ b/data/transactions/logic/evalAppTxn_test.go @@ -37,13 +37,9 @@ func TestActionTypes(t *testing.T) { // good types, not alllowed yet testApp(t, "tx_begin; byte \"keyreg\"; tx_field Type; tx_submit; int 1;", ep, "keyreg is not a valid Type for tx_field") - testApp(t, "tx_begin; byte \"acfg\"; tx_field Type; tx_submit; int 1;", ep, "acfg is not a valid Type for tx_field") - testApp(t, "tx_begin; byte \"afrz\"; tx_field Type; tx_submit; int 1;", ep, "afrz is not a valid Type for tx_field") testApp(t, "tx_begin; byte \"appl\"; tx_field Type; tx_submit; int 1;", ep, "appl is not a valid Type for tx_field") // same, as enums testApp(t, "tx_begin; int keyreg; tx_field TypeEnum; tx_submit; int 1;", ep, "keyreg is not a valid Type for tx_field") - testApp(t, "tx_begin; int acfg; tx_field TypeEnum; tx_submit; int 1;", ep, "acfg is not a valid Type for tx_field") - testApp(t, "tx_begin; int afrz; tx_field TypeEnum; tx_submit; int 1;", ep, "afrz is not a valid Type for tx_field") testApp(t, "tx_begin; int appl; tx_field TypeEnum; tx_submit; int 1;", ep, "appl is not a valid Type for tx_field") testApp(t, "tx_begin; int 42; tx_field TypeEnum; tx_submit; int 1;", ep, "42 is not a valid TypeEnum") testApp(t, "tx_begin; int 0; tx_field TypeEnum; tx_submit; int 1;", ep, "0 is not a valid TypeEnum") @@ -55,12 +51,24 @@ func TestActionTypes(t *testing.T) { testApp(t, "tx_begin; int pay; tx_field TypeEnum; tx_submit; int 1;", ep, "insufficient balance") testApp(t, "tx_begin; int axfer; tx_field TypeEnum; tx_submit; int 1;", ep, "insufficient balance") + testApp(t, "tx_begin; byte \"acfg\"; tx_field Type; tx_submit; int 1;", ep, "insufficient balance") + testApp(t, "tx_begin; byte \"afrz\"; tx_field Type; tx_submit; int 1;", ep, "insufficient balance") + testApp(t, "tx_begin; int acfg; tx_field TypeEnum; tx_submit; int 1;", ep, "insufficient balance") + testApp(t, "tx_begin; int afrz; tx_field TypeEnum; tx_submit; int 1;", ep, "insufficient balance") + // Establish 888 as the app id, and fund it. ledger.NewApp(ep.Txn.Txn.Receiver, 888, basics.AppParams{}) ledger.NewAccount(basics.AppIndex(888).Address(), 200000) testApp(t, "tx_begin; byte \"pay\"; tx_field Type; tx_submit; int 1;", ep) testApp(t, "tx_begin; int pay; tx_field TypeEnum; tx_submit; int 1;", ep) + // Can't submit because we haven't finished setup, but type passes tx_field + testApp(t, "tx_begin; byte \"axfer\"; tx_field Type; int 1;", ep) + testApp(t, "tx_begin; int axfer; tx_field TypeEnum; int 1;", ep) + testApp(t, "tx_begin; byte \"acfg\"; tx_field Type; int 1;", ep) + testApp(t, "tx_begin; int acfg; tx_field TypeEnum; int 1;", ep) + testApp(t, "tx_begin; byte \"afrz\"; tx_field Type; int 1;", ep) + testApp(t, "tx_begin; int afrz; tx_field TypeEnum; int 1;", ep) } func TestFieldTypes(t *testing.T) { @@ -352,3 +360,64 @@ func TestNumInner(t *testing.T) { // In the sample proto, MaxInnerTransactions = 4 testApp(t, pay+pay+pay+pay+pay+";int 1", ep, "tx_submit with MaxInnerTransactions") } + +func TestAssetCreate(t *testing.T) { + create := ` + tx_begin + int acfg + tx_field TypeEnum + int 1000000 + tx_field ConfigAssetTotal + int 3 + tx_field ConfigAssetDecimals + byte "oz" + tx_field ConfigAssetUnitName + byte "Gold" + tx_field ConfigAssetName + byte "https:://gold.rush/" + tx_field ConfigAssetURL + tx_submit + int 1 +` + ep, ledger := makeSampleEnv() + ledger.NewApp(ep.Txn.Txn.Receiver, 888, basics.AppParams{}) + testApp(t, create, ep, "insufficient balance") + // Give it enough for fee. Recall that we don't check min balance at this level. + ledger.NewAccount(ledger.ApplicationID().Address(), defaultEvalProto().MinTxnFee) + testApp(t, create, ep) +} + +func TestAssetFreeze(t *testing.T) { + create := ` + tx_begin + int acfg ; tx_field TypeEnum + int 1000000 ; tx_field ConfigAssetTotal + int 3 ; tx_field ConfigAssetDecimals + byte "oz" ; tx_field ConfigAssetUnitName + byte "Gold" ; tx_field ConfigAssetName + byte "https:://gold.rush/" ; tx_field ConfigAssetURL + global CurrentApplicationAddress ; tx_field ConfigAssetFreeze; + tx_submit + int 1 +` + ep, ledger := makeSampleEnv() + ledger.NewApp(ep.Txn.Txn.Receiver, 888, basics.AppParams{}) + // Give it enough for fees. Recall that we don't check min balance at this level. + ledger.NewAccount(ledger.ApplicationID().Address(), 12*defaultEvalProto().MinTxnFee) + testApp(t, create, ep) + + freeze := ` + tx_begin + int afrz ; tx_field TypeEnum + int 889 ; tx_field FreezeAsset + int 1 ; tx_field FreezeAssetFrozen + txn Accounts 1 ; tx_field FreezeAssetAccount + tx_submit + int 1 +` + testApp(t, freeze, ep, "invalid Asset reference") + ep.Txn.Txn.ForeignAssets = []basics.AssetIndex{basics.AssetIndex(889)} + testApp(t, freeze, ep, "does not hold Asset") + ledger.NewHolding(ep.Txn.Txn.Receiver, 889, 55, false) + testApp(t, freeze, ep) +} diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index 1e507d75cb..673b2ff473 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -66,10 +66,14 @@ func defaultEvalProtoWithVersion(version uint64) config.ConsensusParams { MaxInnerTransactions: 4, - // With the addition of tx_perform, which relies on machinery - // outside logic package for validity checking, we need a more - // realistic set of consensus paramaters. - Asset: true, + // With the addition of tx_field, tx_submit, which rely on + // machinery outside logic package for validity checking, we + // need a more realistic set of consensus paramaters. + Asset: true, + MaxAssetNameBytes: 12, + MaxAssetUnitNameBytes: 6, + MaxAssetURLBytes: 32, + MaxAssetDecimals: 4, } } diff --git a/data/transactions/logic/fields.go b/data/transactions/logic/fields.go index 69c9de58e1..c66538d41a 100644 --- a/data/transactions/logic/fields.go +++ b/data/transactions/logic/fields.go @@ -212,21 +212,21 @@ var txnFieldSpecs = []txnFieldSpec{ {ApprovalProgram, StackBytes, 2, 0}, {ClearStateProgram, StackBytes, 2, 0}, {RekeyTo, StackBytes, 2, 0}, - {ConfigAsset, StackUint64, 2, 0}, - {ConfigAssetTotal, StackUint64, 2, 0}, - {ConfigAssetDecimals, StackUint64, 2, 0}, - {ConfigAssetDefaultFrozen, StackUint64, 2, 0}, - {ConfigAssetUnitName, StackBytes, 2, 0}, - {ConfigAssetName, StackBytes, 2, 0}, - {ConfigAssetURL, StackBytes, 2, 0}, - {ConfigAssetMetadataHash, StackBytes, 2, 0}, - {ConfigAssetManager, StackBytes, 2, 0}, - {ConfigAssetReserve, StackBytes, 2, 0}, - {ConfigAssetFreeze, StackBytes, 2, 0}, - {ConfigAssetClawback, StackBytes, 2, 0}, - {FreezeAsset, StackUint64, 2, 0}, - {FreezeAssetAccount, StackBytes, 2, 0}, - {FreezeAssetFrozen, StackUint64, 2, 0}, + {ConfigAsset, StackUint64, 2, 5}, + {ConfigAssetTotal, StackUint64, 2, 5}, + {ConfigAssetDecimals, StackUint64, 2, 5}, + {ConfigAssetDefaultFrozen, StackUint64, 2, 5}, + {ConfigAssetUnitName, StackBytes, 2, 5}, + {ConfigAssetName, StackBytes, 2, 5}, + {ConfigAssetURL, StackBytes, 2, 5}, + {ConfigAssetMetadataHash, StackBytes, 2, 5}, + {ConfigAssetManager, StackBytes, 2, 5}, + {ConfigAssetReserve, StackBytes, 2, 5}, + {ConfigAssetFreeze, StackBytes, 2, 5}, + {ConfigAssetClawback, StackBytes, 2, 5}, + {FreezeAsset, StackUint64, 2, 5}, + {FreezeAssetAccount, StackBytes, 2, 5}, + {FreezeAssetFrozen, StackUint64, 2, 5}, {Assets, StackUint64, 3, 0}, {NumAssets, StackUint64, 3, 0}, {Applications, StackUint64, 3, 0}, @@ -261,6 +261,8 @@ var txnaFieldSpecByField = map[TxnField]txnFieldSpec{ var innerTxnTypes = map[string]protocol.TxType{ string(protocol.PaymentTx): protocol.PaymentTx, string(protocol.AssetTransferTx): protocol.AssetTransferTx, + string(protocol.AssetConfigTx): protocol.AssetConfigTx, + string(protocol.AssetFreezeTx): protocol.AssetFreezeTx, } // TxnTypeNames is the values of Txn.Type in enum order diff --git a/data/transactions/logictest/ledger.go b/data/transactions/logictest/ledger.go index e7e2ec3d69..b31b0b30a4 100644 --- a/data/transactions/logictest/ledger.go +++ b/data/transactions/logictest/ledger.go @@ -140,6 +140,20 @@ func (l *Ledger) NewAsset(creator basics.Address, assetID basics.AssetIndex, par l.balances[creator] = br } +// freshID gets a new creatable ID that isn't in use +func (l *Ledger) freshID() uint64 { + for try := l.appID + 1; true; try++ { + if _, ok := l.assets[basics.AssetIndex(try)]; ok { + continue + } + if _, ok := l.applications[basics.AppIndex(try)]; ok { + continue + } + return uint64(try) + } + panic("wow") +} + // NewHolding sets the ASA balance of a given account. func (l *Ledger) NewHolding(addr basics.Address, assetID uint64, amount uint64, frozen bool) { br, ok := l.balances[addr] @@ -635,6 +649,43 @@ func (l *Ledger) axfer(from basics.Address, xfer transactions.AssetTransferTxnFi return nil } +func (l *Ledger) acfg(from basics.Address, cfg transactions.AssetConfigTxnFields) error { + if cfg.ConfigAsset == 0 { + l.NewAsset(from, basics.AssetIndex(l.freshID()), cfg.AssetParams) + return nil + } + // This is just a mock. We don't check all the rules about + // not setting fields that have been zeroed. Nor do we keep + // anything from before. + l.assets[cfg.ConfigAsset] = asaParams{ + Creator: from, + AssetParams: cfg.AssetParams, + } + return nil +} + +func (l *Ledger) afrz(from basics.Address, frz transactions.AssetFreezeTxnFields) error { + aid := frz.FreezeAsset + params, ok := l.assets[aid] + if !ok { + return fmt.Errorf("Asset (%d) does not exist", aid) + } + if params.Freeze != from { + return fmt.Errorf("Asset (%d) can not be frozen by %s", aid, from) + } + br, ok := l.balances[frz.FreezeAccount] + if !ok { + return fmt.Errorf("%s does not hold anything", from) + } + holding, ok := br.holdings[aid] + if !ok { + return fmt.Errorf("%s does not hold Asset (%d)", from, aid) + } + holding.Frozen = frz.AssetFrozen + br.holdings[aid] = holding + return nil +} + /* It's gross to reimplement this here, rather than have a way to use a ledger that's backed by our mock, but uses the "real" code (cowRoundState which implements Balances), as a better test. To @@ -659,6 +710,10 @@ func (l *Ledger) Perform(txn *transactions.Transaction, spec transactions.Specia err = l.pay(txn.Sender, txn.PaymentTxnFields) case protocol.AssetTransferTx: err = l.axfer(txn.Sender, txn.AssetTransferTxnFields) + case protocol.AssetConfigTx: + err = l.acfg(txn.Sender, txn.AssetConfigTxnFields) + case protocol.AssetFreezeTx: + err = l.afrz(txn.Sender, txn.AssetFreezeTxnFields) default: err = fmt.Errorf("%s txn in AVM", txn.Type) } diff --git a/ledger/applications.go b/ledger/applications.go index e8ce84ad31..9b3cb27b47 100644 --- a/ledger/applications.go +++ b/ledger/applications.go @@ -45,6 +45,8 @@ type cowForLogicLedger interface { round() basics.Round prevTimestamp() int64 allocated(addr basics.Address, aidx basics.AppIndex, global bool) (bool, error) + txnCounter() uint64 + incTxnCount() } func newLogicLedger(cow cowForLogicLedger, aidx basics.AppIndex) (*logicLedger, error) { @@ -271,11 +273,26 @@ func (al *logicLedger) Perform(tx *transactions.Transaction, spec transactions.S return ad, err } + // compared to eval.transaction() it may seem strange that we + // increment the transaction count *before* transaction + // processing, rather than after. But we need to account for the + // fact that our outer transaction has not yet incremented their + // count (in addTx()), so we need to increment ahead of use, so we + // don't use the same index. If eval.transaction() incremented + // ahead of processing, we'd have to do ours *after* so that we'd + // use the next id. So either way, this would seem backwards at + // first glance. + al.cow.incTxnCount() + switch tx.Type { case protocol.PaymentTx: err = apply.Payment(tx.PaymentTxnFields, tx.Header, balances, spec, &ad) case protocol.AssetTransferTx: err = apply.AssetTransfer(tx.AssetTransferTxnFields, tx.Header, balances, spec, &ad) + case protocol.AssetConfigTx: + err = apply.AssetConfig(tx.AssetConfigTxnFields, tx.Header, balances, spec, &ad, al.cow.txnCounter()) + case protocol.AssetFreezeTx: + err = apply.AssetFreeze(tx.AssetFreezeTxnFields, tx.Header, balances, spec, &ad) default: err = fmt.Errorf("%s tx in AVM", tx.Type) } diff --git a/ledger/applications_test.go b/ledger/applications_test.go index 43bdca4317..83b7471ecb 100644 --- a/ledger/applications_test.go +++ b/ledger/applications_test.go @@ -61,6 +61,7 @@ type mockCowForLogicLedger struct { brs map[basics.Address]basics.AccountData stores map[storeLocator]basics.TealKeyValue tcs map[int]basics.CreatableIndex + txc uint64 } func (c *mockCowForLogicLedger) Get(addr basics.Address, withPendingRewards bool) (basics.AccountData, error) { @@ -126,6 +127,14 @@ func (c *mockCowForLogicLedger) allocated(addr basics.Address, aidx basics.AppIn return found, nil } +func (c *mockCowForLogicLedger) incTxnCount() { + c.txc++ +} + +func (c *mockCowForLogicLedger) txnCounter() uint64 { + return c.txc +} + func newCowMock(creatables []modsData) *mockCowForLogicLedger { var m mockCowForLogicLedger m.cr = make(map[creatableLocator]basics.Address, len(creatables)) diff --git a/ledger/apptxn_test.go b/ledger/apptxn_test.go index 005916fe62..1095363512 100644 --- a/ledger/apptxn_test.go +++ b/ledger/apptxn_test.go @@ -632,7 +632,7 @@ func TestRekeyActionCloseAccount(t *testing.T) { l.endBlock(t, eval) } -// TestPayAction ensures a pay in teal affects balances +// TestDuplicatePayAction shows two pays with same parameters can be done as inner tarnsactions func TestDuplicatePayAction(t *testing.T) { partitiontest.PartitionTest(t) @@ -754,3 +754,141 @@ func TestInnerTxnCount(t *testing.T) { vb = l.endBlock(t, eval) require.Equal(t, 4, int(vb.blk.TxnCounter)) } + +// TestAcfgAction ensures assets can be created and configured in teal +func TestAcfgAction(t *testing.T) { + partitiontest.PartitionTest(t) + + genBalances, addrs, _ := newTestGenesis() + l := newTestLedger(t, genBalances) + defer l.Close() + + appIndex := basics.AppIndex(1) + app := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApprovalProgram: main(` + tx_begin + int acfg + tx_field TypeEnum + + txn ApplicationArgs 0 + byte "create" + == + bz manager + int 1000000 + tx_field ConfigAssetTotal + int 3 + tx_field ConfigAssetDecimals + byte "oz" + tx_field ConfigAssetUnitName + byte "Gold" + tx_field ConfigAssetName + byte "https:://gold.rush/" + tx_field ConfigAssetURL + global CurrentApplicationAddress + dup + dup2 + tx_field ConfigAssetManager + tx_field ConfigAssetReserve + tx_field ConfigAssetFreeze + tx_field ConfigAssetClawback + b submit +manager: + txn ApplicationArgs 0 + byte "manager" + == + bz reserve +reserve: + txn ApplicationArgs 0 + byte "reserve" + == + bz freeze +freeze: + txn ApplicationArgs 0 + byte "freeze" + == + bz clawback +clawback: + txn ApplicationArgs 0 + byte "manager" + == +submit: tx_submit +`), + } + + fund := txntest.Txn{ + Type: "pay", + Sender: addrs[0], + Receiver: appIndex.Address(), + Amount: 200000, // exactly account min balance + one asset + } + + eval := l.nextBlock(t) + eval.txns(t, &app, &fund) + l.endBlock(t, eval) + + create_asa := txntest.Txn{ + Type: "appl", + Sender: addrs[1], + ApplicationID: appIndex, + ApplicationArgs: [][]byte{[]byte("create")}, + } + + eval = l.nextBlock(t) + // Can't create an asset if you have exactly 200,000 and need to pay fee + eval.txn(t, &create_asa, "balance 199000 below min 200000") + // fund it some more and try again + eval.txns(t, fund.Noted("more!"), &create_asa) + vb := l.endBlock(t, eval) + + asaIndex := vb.blk.Payset[1].EvalDelta.InnerTxns[0].ConfigAsset + require.Equal(t, basics.AssetIndex(5), asaIndex) +} + +// TestAsaDuringInit ensures an ASA can be made while initilizing an +// app. In practice, this is impossible, because you would not be +// able to prefund the account - you don't know the app id. But here +// we can know, so it helps exercise txncounter changes. +func TestAsaDuringInit(t *testing.T) { + partitiontest.PartitionTest(t) + + genBalances, addrs, _ := newTestGenesis() + l := newTestLedger(t, genBalances) + defer l.Close() + + appIndex := basics.AppIndex(2) + prefund := txntest.Txn{ + Type: "pay", + Sender: addrs[0], + Receiver: appIndex.Address(), + Amount: 300000, // plenty for min balances, fees + } + + app := txntest.Txn{ + Type: "appl", + Sender: addrs[0], + ApprovalProgram: ` + tx_begin + int acfg + tx_field TypeEnum + int 1000000 + tx_field ConfigAssetTotal + byte "oz" + tx_field ConfigAssetUnitName + byte "Gold" + tx_field ConfigAssetName + tx_submit + int 1 +`, + } + + eval := l.nextBlock(t) + eval.txns(t, &prefund, &app) + vb := l.endBlock(t, eval) + + require.Equal(t, appIndex, vb.blk.Payset[1].ApplicationID) + + asaIndex := vb.blk.Payset[1].EvalDelta.InnerTxns[0].ConfigAsset + require.Equal(t, basics.AssetIndex(3), asaIndex) +} diff --git a/ledger/cow.go b/ledger/cow.go index 91b4089fe9..1234c191b4 100644 --- a/ledger/cow.go +++ b/ledger/cow.go @@ -202,9 +202,13 @@ func (cb *roundCowState) trackCreatable(creatableIndex basics.CreatableIndex) { cb.trackedCreatables[cb.groupIdx] = creatableIndex } -func (cb *roundCowState) addTx(txn transactions.Transaction, txid transactions.Txid, inners uint64) { +func (cb *roundCowState) incTxnCount() { + cb.txnCount++ +} + +func (cb *roundCowState) addTx(txn transactions.Transaction, txid transactions.Txid) { cb.mods.Txids[txid] = txn.LastValid - cb.txnCount += 1 + inners + cb.incTxnCount() if txn.Lease != [32]byte{} { cb.mods.Txleases[ledgercore.Txlease{Sender: txn.Sender, Lease: txn.Lease}] = txn.LastValid } diff --git a/ledger/eval.go b/ledger/eval.go index acd5f62204..4b04244806 100644 --- a/ledger/eval.go +++ b/ledger/eval.go @@ -559,8 +559,8 @@ func (eval *BlockEvaluator) workaroundOverspentRewards(rewardPoolBalance basics. return } -// TxnCounter returns the number of transactions that have been added to the block evaluator so far. -func (eval *BlockEvaluator) TxnCounter() int { +// PaySetSize returns the number of top-level transactions that have been added to the block evaluator so far. +func (eval *BlockEvaluator) PaySetSize() int { return len(eval.block.Payset) } @@ -912,7 +912,7 @@ func (eval *BlockEvaluator) transaction(txn transactions.SignedTxn, evalParams * } // Remember this txn - cow.addTx(txn.Txn, txid, uint64(len(applyData.EvalDelta.InnerTxns))) // Will need to be recursive when inners are + cow.addTx(txn.Txn, txid) return nil } From 41d379d67107afe293d8dba6adbb6077c3ba52e8 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Sat, 11 Sep 2021 20:25:21 -0400 Subject: [PATCH 11/28] Lint & Jason P. Perfect together --- data/transactions/logic/evalAppTxn_test.go | 18 ++++++++++++++---- ledger/apptxn_test.go | 18 ++++++++++++++---- ledger/eval_test.go | 15 +++++++++++++++ 3 files changed, 43 insertions(+), 8 deletions(-) diff --git a/data/transactions/logic/evalAppTxn_test.go b/data/transactions/logic/evalAppTxn_test.go index a48ce5ca29..3340afdc47 100644 --- a/data/transactions/logic/evalAppTxn_test.go +++ b/data/transactions/logic/evalAppTxn_test.go @@ -21,6 +21,7 @@ import ( "testing" "github.com/algorand/go-algorand/data/basics" + "github.com/stretchr/testify/require" ) func TestActionTypes(t *testing.T) { @@ -408,16 +409,25 @@ func TestAssetFreeze(t *testing.T) { freeze := ` tx_begin - int afrz ; tx_field TypeEnum - int 889 ; tx_field FreezeAsset - int 1 ; tx_field FreezeAssetFrozen - txn Accounts 1 ; tx_field FreezeAssetAccount + int afrz ; tx_field TypeEnum + int 889 ; tx_field FreezeAsset + txn ApplicationArgs 0; btoi ; tx_field FreezeAssetFrozen + txn Accounts 1 ; tx_field FreezeAssetAccount tx_submit int 1 ` testApp(t, freeze, ep, "invalid Asset reference") ep.Txn.Txn.ForeignAssets = []basics.AssetIndex{basics.AssetIndex(889)} + ep.Txn.Txn.ApplicationArgs = [][]byte{{0x01}} testApp(t, freeze, ep, "does not hold Asset") ledger.NewHolding(ep.Txn.Txn.Receiver, 889, 55, false) testApp(t, freeze, ep) + holding, err := ledger.AssetHolding(ep.Txn.Txn.Receiver, 889) + require.NoError(t, err) + require.Equal(t, true, holding.Frozen) + ep.Txn.Txn.ApplicationArgs = [][]byte{{0x00}} + testApp(t, freeze, ep) + holding, err = ledger.AssetHolding(ep.Txn.Txn.Receiver, 889) + require.NoError(t, err) + require.Equal(t, false, holding.Frozen) } diff --git a/ledger/apptxn_test.go b/ledger/apptxn_test.go index 1095363512..10ce49a087 100644 --- a/ledger/apptxn_test.go +++ b/ledger/apptxn_test.go @@ -821,14 +821,14 @@ submit: tx_submit Type: "pay", Sender: addrs[0], Receiver: appIndex.Address(), - Amount: 200000, // exactly account min balance + one asset + Amount: 200_000, // exactly account min balance + one asset } eval := l.nextBlock(t) eval.txns(t, &app, &fund) l.endBlock(t, eval) - create_asa := txntest.Txn{ + createAsa := txntest.Txn{ Type: "appl", Sender: addrs[1], ApplicationID: appIndex, @@ -837,13 +837,23 @@ submit: tx_submit eval = l.nextBlock(t) // Can't create an asset if you have exactly 200,000 and need to pay fee - eval.txn(t, &create_asa, "balance 199000 below min 200000") + eval.txn(t, &createAsa, "balance 199000 below min 200000") // fund it some more and try again - eval.txns(t, fund.Noted("more!"), &create_asa) + eval.txns(t, fund.Noted("more!"), &createAsa) vb := l.endBlock(t, eval) asaIndex := vb.blk.Payset[1].EvalDelta.InnerTxns[0].ConfigAsset require.Equal(t, basics.AssetIndex(5), asaIndex) + + asaParams, err := l.asaParams(t, basics.AssetIndex(5)) + require.NoError(t, err) + + require.Equal(t, 1_000_000, int(asaParams.Total)) + require.Equal(t, 3, int(asaParams.Decimals)) + require.Equal(t, "oz", asaParams.UnitName) + require.Equal(t, "Gold", asaParams.AssetName) + require.Equal(t, "https:://gold.rush/", asaParams.URL) + } // TestAsaDuringInit ensures an ASA can be made while initilizing an diff --git a/ledger/eval_test.go b/ledger/eval_test.go index cdaf600bdb..2d43e36ad5 100644 --- a/ledger/eval_test.go +++ b/ledger/eval_test.go @@ -1441,6 +1441,21 @@ func (ledger *Ledger) asa(t testing.TB, addr basics.Address, asset basics.AssetI return 0, false } +// asaParams gets the asset params for a given asa index +func (ledger *Ledger) asaParams(t testing.TB, asset basics.AssetIndex) (basics.AssetParams, error) { + creator, ok, err := ledger.GetCreator(basics.CreatableIndex(asset), basics.AssetCreatable) + if err != nil { + return basics.AssetParams{}, err + } + if !ok { + return basics.AssetParams{}, fmt.Errorf("no asset") + } + if params, ok := ledger.lookup(t, creator).AssetParams[asset]; ok { + return params, nil + } + return basics.AssetParams{}, fmt.Errorf("bad lookup") +} + func (eval *BlockEvaluator) fillDefaults(txn *txntest.Txn) { if txn.GenesisHash.IsZero() { txn.GenesisHash = eval.genesisHash From 7e4f50a7265b4e1be35ef4d91d4b6617ddb23776 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Mon, 13 Sep 2021 07:43:15 -0400 Subject: [PATCH 12/28] CR fixups --- data/transactions/logic/eval.go | 13 +++++++------ data/transactions/logic/evalAppTxn_test.go | 5 +++-- data/transactions/transaction.go | 1 + ledger/apptxn_test.go | 4 ++-- ledger/eval_test.go | 4 ++-- 5 files changed, 15 insertions(+), 12 deletions(-) diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 332069c596..92a5555fc9 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -119,13 +119,14 @@ func (sv *stackValue) bool() (bool, error) { if err != nil { return false, err } - if u64 == 1 { - return true, nil - } - if u64 == 0 { + switch u64 { + case 0: return false, nil + case 1: + return true, nil + default: + return false, fmt.Errorf("boolean is neither 1 nor 0: %d", u64) } - return false, fmt.Errorf("boolean is neither 1 nor 0: %d", u64) } func (sv *stackValue) string(limit int) (string, error) { @@ -3558,7 +3559,7 @@ func (cx *EvalContext) stackIntoTxnField(sv stackValue, fs txnFieldSpec, txn *tr decimals, err = sv.uint() if err == nil { if decimals > uint64(cx.Proto.MaxAssetDecimals) { - err = fmt.Errorf("too many decimals") + err = fmt.Errorf("too many decimals (%d)", decimals) } else { txn.AssetParams.Decimals = uint32(decimals) } diff --git a/data/transactions/logic/evalAppTxn_test.go b/data/transactions/logic/evalAppTxn_test.go index 3340afdc47..26d58e62bf 100644 --- a/data/transactions/logic/evalAppTxn_test.go +++ b/data/transactions/logic/evalAppTxn_test.go @@ -21,6 +21,7 @@ import ( "testing" "github.com/algorand/go-algorand/data/basics" + "github.com/stretchr/testify/require" ) @@ -375,7 +376,7 @@ func TestAssetCreate(t *testing.T) { tx_field ConfigAssetUnitName byte "Gold" tx_field ConfigAssetName - byte "https:://gold.rush/" + byte "https://gold.rush/" tx_field ConfigAssetURL tx_submit int 1 @@ -396,7 +397,7 @@ func TestAssetFreeze(t *testing.T) { int 3 ; tx_field ConfigAssetDecimals byte "oz" ; tx_field ConfigAssetUnitName byte "Gold" ; tx_field ConfigAssetName - byte "https:://gold.rush/" ; tx_field ConfigAssetURL + byte "https://gold.rush/" ; tx_field ConfigAssetURL global CurrentApplicationAddress ; tx_field ConfigAssetFreeze; tx_submit int 1 diff --git a/data/transactions/transaction.go b/data/transactions/transaction.go index 2a5fe327a9..fb356bb04d 100644 --- a/data/transactions/transaction.go +++ b/data/transactions/transaction.go @@ -116,6 +116,7 @@ type ApplyData struct { // If asa or app is being created, the id used. Else 0. // Names chosen to match naming the corresponding txn. + // These are populated on when MaxInnerTransactions > 0 (TEAL 5) ConfigAsset basics.AssetIndex `codec:"caid"` ApplicationID basics.AppIndex `codec:"apid"` } diff --git a/ledger/apptxn_test.go b/ledger/apptxn_test.go index 10ce49a087..2683447322 100644 --- a/ledger/apptxn_test.go +++ b/ledger/apptxn_test.go @@ -784,7 +784,7 @@ func TestAcfgAction(t *testing.T) { tx_field ConfigAssetUnitName byte "Gold" tx_field ConfigAssetName - byte "https:://gold.rush/" + byte "https://gold.rush/" tx_field ConfigAssetURL global CurrentApplicationAddress dup @@ -852,7 +852,7 @@ submit: tx_submit require.Equal(t, 3, int(asaParams.Decimals)) require.Equal(t, "oz", asaParams.UnitName) require.Equal(t, "Gold", asaParams.AssetName) - require.Equal(t, "https:://gold.rush/", asaParams.URL) + require.Equal(t, "https://gold.rush/", asaParams.URL) } diff --git a/ledger/eval_test.go b/ledger/eval_test.go index 2d43e36ad5..865ea209e9 100644 --- a/ledger/eval_test.go +++ b/ledger/eval_test.go @@ -1448,12 +1448,12 @@ func (ledger *Ledger) asaParams(t testing.TB, asset basics.AssetIndex) (basics.A return basics.AssetParams{}, err } if !ok { - return basics.AssetParams{}, fmt.Errorf("no asset") + return basics.AssetParams{}, fmt.Errorf("no asset (%d)", asset) } if params, ok := ledger.lookup(t, creator).AssetParams[asset]; ok { return params, nil } - return basics.AssetParams{}, fmt.Errorf("bad lookup") + return basics.AssetParams{}, fmt.Errorf("bad lookup (%d)", asset) } func (eval *BlockEvaluator) fillDefaults(txn *txntest.Txn) { From abcff11904a9128dfd94010cc9062711d107d4dd Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Mon, 13 Sep 2021 07:53:08 -0400 Subject: [PATCH 13/28] Correct outdated comment --- data/transactions/logic/eval.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 92a5555fc9..62304e9fb4 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -3675,7 +3675,9 @@ func opTxSubmit(cx *EvalContext) { if cx.FeeCredit != nil && *cx.FeeCredit >= underpaid { *cx.FeeCredit -= underpaid } else { - // This should be impossible until we allow changing the Fee + // We allow changing the fee. One pattern might be for an + // app to unilaterally set its Fee to 0. The idea would be + // that other transactions were supposed to overpay. cx.err = fmt.Errorf("fee too small") return } From 5222a368c26b0cc5f64fd6fafb8ea527dc60c9db Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Mon, 13 Sep 2021 09:38:44 -0400 Subject: [PATCH 14/28] Prose changes --- data/transactions/logic/README.md | 49 +++++++++++++++++++--------- data/transactions/logic/README_in.md | 41 ++++++++++++++++------- 2 files changed, 62 insertions(+), 28 deletions(-) diff --git a/data/transactions/logic/README.md b/data/transactions/logic/README.md index 345e72186b..5ce7dd3951 100644 --- a/data/transactions/logic/README.md +++ b/data/transactions/logic/README.md @@ -1,7 +1,7 @@ # Transaction Execution Approval Language (TEAL) TEAL is a bytecode based stack language that executes inside Algorand transactions. TEAL programs can be used to check the parameters of the transaction and approve the transaction as if by a signature. This use of TEAL is called a _LogicSig_. Starting with v2, TEAL programs may -also execute as _Applications_ which are invoked with explicit application call transactions. Programs have read-only access to the transaction they are attached to, transactions in their atomic transaction group, and a few global values. In addition, _Application_ programs have access to limited state that is global to the application and per-account local state for each account that has opted-in to the application. Programs cannot modify or create transactions, only reject or approve them. For both types of program, approval is signaled by finishing with the stack containing a single non-zero uint64 value. +also execute as _Applications_ which are invoked with explicit application call transactions. Programs have read-only access to the transaction they are attached to, transactions in their atomic transaction group, and a few global values. In addition, _Application_ programs have access to limited state that is global to the application and per-account local state for each account that has opted-in to the application. For both types of program, approval is signaled by finishing with the stack containing a single non-zero uint64 value. ## The Stack @@ -11,7 +11,10 @@ The maximum stack depth is currently 1000. ## Scratch Space -In addition to the stack there are 256 positions of scratch space, also uint64-bytes union values, accessed by the `load` and `store` ops moving data from or to scratch space, respectively. +In addition to the stack there are 256 positions of scratch space, +also uint64-bytes union values, each initialized as uint64 +zero. Scratch space is acccesed by the `load(s)` and `store(s)` ops +moving data from or to scratch space, respectively. ## Execution Modes @@ -30,14 +33,14 @@ TEAL LogicSigs run in Algorand nodes as part of testing a proposed transaction t If an authorized program executes and finishes with a single non-zero uint64 value on the stack then that program has validated the transaction it is attached to. -The TEAL program has access to data from the transaction it is attached to (`txn` op), any transactions in a transaction group it is part of (`gtxn` op), and a few global values like consensus parameters (`global` op). Some "Args" may be attached to a transaction being validated by a TEAL program. Args are an array of byte strings. A common pattern would be to have the key to unlock some contract as an Arg. Args are recorded on the blockchain and publicly visible when the transaction is submitted to the network. +The TEAL program has access to data from the transaction it is attached to (`txn` op), any transactions in a transaction group it is part of (`gtxn` op), and a few global values like consensus parameters (`global` op). Some "Args" may be attached to a transaction being validated by a TEAL program. Args are an array of byte strings. A common pattern would be to have the key to unlock some contract as an Arg. Args are recorded on the blockchain and publicly visible when the transaction is submitted to the network. These LogicSig Args are _not_signed. A program can either authorize some delegated action on a normal private key signed or multisig account or be wholly in charge of a contract account. * If the account has signed the program (an ed25519 signature on "Program" concatenated with the program bytes) then if the program returns true the transaction is authorized as if the account had signed it. This allows an account to hand out a signed program so that other users can carry out delegated actions which are approved by the program. * If the SHA512_256 hash of the program (prefixed by "Program") is equal to the transaction Sender address then this is a contract account wholly controlled by the program. No other signature is necessary or possible. The only way to execute a transaction against the contract account is for the program to approve it. -The TEAL bytecode plus the length of any Args must add up to less than 1000 bytes (consensus parameter LogicSigMaxSize). Each TEAL op has an associated cost and the program cost must total less than 20000 (consensus parameter LogicSigMaxCost). Most ops have a cost of 1, but a few slow crypto ops are much higher. Prior to v4, the program's cost was estimated as the static sum of all the opcode costs in the program (whether they were actually executed or not). Beginning with v4, the program's cost is tracked dynamically, while being evaluated. If the program exceeds its budget, it fails. +The TEAL bytecode plus the length of all Args must add up to no more than 1000 bytes (consensus parameter LogicSigMaxSize). Each TEAL op has an associated cost and the program cost must total no more than 20000 (consensus parameter LogicSigMaxCost). Most ops have a cost of 1, but a few slow crypto ops are much higher. Prior to v4, the program's cost was estimated as the static sum of all the opcode costs in the program (whether they were actually executed or not). Beginning with v4, the program's cost is tracked dynamically, while being evaluated. If the program exceeds its budget, it fails. ## Constants @@ -86,7 +89,7 @@ Many programs need only a few dozen instructions. The instruction set has some o This summary is supplemented by more detail in the [opcodes document](TEAL_opcodes.md). -Some operations 'panic' and immediately end execution of the program. +Some operations 'panic' and immediately fail the program. A transaction checked by a program that panics is not valid. A contract account governed by a buggy program might not have a way to get assets back out of it. Code carefully. @@ -146,6 +149,8 @@ For three-argument ops, `A` is the element two below the top, `B` is the penulti These opcodes return portions of byte arrays, accessed by position, in various sizes. +### Byte Array Manipulation + | Op | Description | | --- | --- | | `substring s e` | pop a byte-array A. For immediate values in 0..255 S and E: extract a range of bytes from A starting at S up to but not including E, push the substring result. If E < S, or either is larger than the array length, the program fails | @@ -195,16 +200,6 @@ these results may contain leading zero bytes. | `b^` | A bitwise-xor B, where A and B are byte-arrays, zero-left extended to the greater of their lengths | | `b~` | X with all bits inverted | -The following opcodes allow for the construction and submission of -"inner transaction" - -| Op | Description | -| --- | --- | -| `tx_begin` | Begin preparation of a new inner transaction | -| `tx_field f` | Set field F of the current inner transaction to X. Fail if X is the wrong type for F. | -| `tx_submit` | Execute the current inner transaction. Fail if 16 inner transactions have already been executed, or if the transaction fails | - - ### Loading Values Opcodes for getting data onto the stack. @@ -423,6 +418,29 @@ App fields used in the `app_params_get` opcode. | `app_params_get i` | read from app A params field X (imm arg) => {0 or 1 (top), value} | | `log` | write bytes to log state of the current application | +### Inner Transactions + +The following opcodes allow for "inner transactions". Inner +transactions allow stateful applications to have many of the effects +of a true top-level transaction, programatically. However, they are +different in significant ways. The most important differences are +that they are not signed, and do not appear in the block in the usual +away. Instead, their effects are noted in metadata associated with the +associated top-level application call transaction. An inner +transaction's `Sender` must be the SHA512_256 hash of the application +ID (prefixed by "appID"), or an account that has been rekeyed to that +hash. + +Currently, inner transactions may perform `pay`, `axfer`, `acfg`, and +`afrz` effects. + +| Op | Description | +| --- | --- | +| `tx_begin` | Begin preparation of a new inner transaction | +| `tx_field f` | Set field F of the current inner transaction to X. Fail if X is the wrong type for F. | +| `tx_submit` | Execute the current inner transaction. Fail if 16 inner transactions have already been executed, or if the transaction fails | + + # Assembler Syntax The assembler parses line by line. Ops that just use the stack appear on a line by themselves. Ops that take arguments are the op and then whitespace and then any argument or arguments. @@ -493,7 +511,6 @@ A '[proto-buf style variable length unsigned int](https://developers.google.com/ Design and implementation limitations to be aware of with various versions of TEAL. -* TEAL cannot create or change a transaction, only approve or reject. * Stateless TEAL cannot lookup balances of Algos or other assets. (Standard transaction accounting will apply after TEAL has run and authorized a transaction. A TEAL-approved transaction could still be invalid by other accounting rules just as a standard signed transaction could be invalid. e.g. I can't give away money I don't have.) * TEAL cannot access information in previous blocks. TEAL cannot access most information in other transactions in the current block. (TEAL can access fields of the transaction it is attached to and the transactions in an atomic transaction group.) * TEAL cannot know exactly what round the current transaction will commit in (but it is somewhere in FirstValid through LastValid). diff --git a/data/transactions/logic/README_in.md b/data/transactions/logic/README_in.md index 86b3d2ea88..1a90106fe6 100644 --- a/data/transactions/logic/README_in.md +++ b/data/transactions/logic/README_in.md @@ -1,7 +1,7 @@ # Transaction Execution Approval Language (TEAL) TEAL is a bytecode based stack language that executes inside Algorand transactions. TEAL programs can be used to check the parameters of the transaction and approve the transaction as if by a signature. This use of TEAL is called a _LogicSig_. Starting with v2, TEAL programs may -also execute as _Applications_ which are invoked with explicit application call transactions. Programs have read-only access to the transaction they are attached to, transactions in their atomic transaction group, and a few global values. In addition, _Application_ programs have access to limited state that is global to the application and per-account local state for each account that has opted-in to the application. Programs cannot modify or create transactions, only reject or approve them. For both types of program, approval is signaled by finishing with the stack containing a single non-zero uint64 value. +also execute as _Applications_ which are invoked with explicit application call transactions. Programs have read-only access to the transaction they are attached to, transactions in their atomic transaction group, and a few global values. In addition, _Application_ programs have access to limited state that is global to the application and per-account local state for each account that has opted-in to the application. For both types of program, approval is signaled by finishing with the stack containing a single non-zero uint64 value. ## The Stack @@ -11,7 +11,10 @@ The maximum stack depth is currently 1000. ## Scratch Space -In addition to the stack there are 256 positions of scratch space, also uint64-bytes union values, accessed by the `load` and `store` ops moving data from or to scratch space, respectively. +In addition to the stack there are 256 positions of scratch space, +also uint64-bytes union values, each initialized as uint64 +zero. Scratch space is acccesed by the `load(s)` and `store(s)` ops +moving data from or to scratch space, respectively. ## Execution Modes @@ -30,14 +33,14 @@ TEAL LogicSigs run in Algorand nodes as part of testing a proposed transaction t If an authorized program executes and finishes with a single non-zero uint64 value on the stack then that program has validated the transaction it is attached to. -The TEAL program has access to data from the transaction it is attached to (`txn` op), any transactions in a transaction group it is part of (`gtxn` op), and a few global values like consensus parameters (`global` op). Some "Args" may be attached to a transaction being validated by a TEAL program. Args are an array of byte strings. A common pattern would be to have the key to unlock some contract as an Arg. Args are recorded on the blockchain and publicly visible when the transaction is submitted to the network. +The TEAL program has access to data from the transaction it is attached to (`txn` op), any transactions in a transaction group it is part of (`gtxn` op), and a few global values like consensus parameters (`global` op). Some "Args" may be attached to a transaction being validated by a TEAL program. Args are an array of byte strings. A common pattern would be to have the key to unlock some contract as an Arg. Args are recorded on the blockchain and publicly visible when the transaction is submitted to the network. These LogicSig Args are _not_signed. A program can either authorize some delegated action on a normal private key signed or multisig account or be wholly in charge of a contract account. * If the account has signed the program (an ed25519 signature on "Program" concatenated with the program bytes) then if the program returns true the transaction is authorized as if the account had signed it. This allows an account to hand out a signed program so that other users can carry out delegated actions which are approved by the program. * If the SHA512_256 hash of the program (prefixed by "Program") is equal to the transaction Sender address then this is a contract account wholly controlled by the program. No other signature is necessary or possible. The only way to execute a transaction against the contract account is for the program to approve it. -The TEAL bytecode plus the length of any Args must add up to less than 1000 bytes (consensus parameter LogicSigMaxSize). Each TEAL op has an associated cost and the program cost must total less than 20000 (consensus parameter LogicSigMaxCost). Most ops have a cost of 1, but a few slow crypto ops are much higher. Prior to v4, the program's cost was estimated as the static sum of all the opcode costs in the program (whether they were actually executed or not). Beginning with v4, the program's cost is tracked dynamically, while being evaluated. If the program exceeds its budget, it fails. +The TEAL bytecode plus the length of all Args must add up to no more than 1000 bytes (consensus parameter LogicSigMaxSize). Each TEAL op has an associated cost and the program cost must total no more than 20000 (consensus parameter LogicSigMaxCost). Most ops have a cost of 1, but a few slow crypto ops are much higher. Prior to v4, the program's cost was estimated as the static sum of all the opcode costs in the program (whether they were actually executed or not). Beginning with v4, the program's cost is tracked dynamically, while being evaluated. If the program exceeds its budget, it fails. ## Constants @@ -63,7 +66,7 @@ Many programs need only a few dozen instructions. The instruction set has some o This summary is supplemented by more detail in the [opcodes document](TEAL_opcodes.md). -Some operations 'panic' and immediately end execution of the program. +Some operations 'panic' and immediately fail the program. A transaction checked by a program that panics is not valid. A contract account governed by a buggy program might not have a way to get assets back out of it. Code carefully. @@ -80,6 +83,8 @@ For three-argument ops, `A` is the element two below the top, `B` is the penulti These opcodes return portions of byte arrays, accessed by position, in various sizes. +### Byte Array Manipulation + @@ Byte_Array_Slicing.md @@ These opcodes take byte-array values that are interpreted as @@ -104,12 +109,6 @@ these results may contain leading zero bytes. @@ Byte_Array_Logic.md @@ -The following opcodes allow for the construction and submission of -"inner transaction" - -@@ Inner_Transactions.md @@ - - ### Loading Values Opcodes for getting data onto the stack. @@ -152,6 +151,25 @@ App fields used in the `app_params_get` opcode. @@ State_Access.md @@ +### Inner Transactions + +The following opcodes allow for "inner transactions". Inner +transactions allow stateful applications to have many of the effects +of a true top-level transaction, programatically. However, they are +different in significant ways. The most important differences are +that they are not signed, and do not appear in the block in the usual +away. Instead, their effects are noted in metadata associated with the +associated top-level application call transaction. An inner +transaction's `Sender` must be the SHA512_256 hash of the application +ID (prefixed by "appID"), or an account that has been rekeyed to that +hash. + +Currently, inner transactions may perform `pay`, `axfer`, `acfg`, and +`afrz` effects. + +@@ Inner_Transactions.md @@ + + # Assembler Syntax The assembler parses line by line. Ops that just use the stack appear on a line by themselves. Ops that take arguments are the op and then whitespace and then any argument or arguments. @@ -222,7 +240,6 @@ A '[proto-buf style variable length unsigned int](https://developers.google.com/ Design and implementation limitations to be aware of with various versions of TEAL. -* TEAL cannot create or change a transaction, only approve or reject. * Stateless TEAL cannot lookup balances of Algos or other assets. (Standard transaction accounting will apply after TEAL has run and authorized a transaction. A TEAL-approved transaction could still be invalid by other accounting rules just as a standard signed transaction could be invalid. e.g. I can't give away money I don't have.) * TEAL cannot access information in previous blocks. TEAL cannot access most information in other transactions in the current block. (TEAL can access fields of the transaction it is attached to and the transactions in an atomic transaction group.) * TEAL cannot know exactly what round the current transaction will commit in (but it is somewhere in FirstValid through LastValid). From 68cd49f0fd5d494e4b368cefd4ead973d669fc98 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Mon, 13 Sep 2021 12:06:11 -0400 Subject: [PATCH 15/28] Field versioning --- cmd/opdoc/opdoc.go | 8 ++++---- data/transactions/logic/README.md | 8 ++++---- data/transactions/logic/README_in.md | 2 +- data/transactions/logic/TEAL_opcodes.md | 10 +++++----- data/transactions/logic/doc.go | 26 +++++++++++++++++-------- data/transactions/logic/fields.go | 2 +- 6 files changed, 33 insertions(+), 23 deletions(-) diff --git a/cmd/opdoc/opdoc.go b/cmd/opdoc/opdoc.go index 46a2185da0..8f59960f58 100644 --- a/cmd/opdoc/opdoc.go +++ b/cmd/opdoc/opdoc.go @@ -110,12 +110,12 @@ func assetHoldingFieldsMarkdown(out io.Writer) { func assetParamsFieldsMarkdown(out io.Writer) { fmt.Fprintf(out, "\n`asset_params_get` Fields:\n\n") - fieldTableMarkdown(out, logic.AssetParamsFieldNames, logic.AssetParamsFieldTypes, logic.AssetParamsFieldDocs) + fieldTableMarkdown(out, logic.AssetParamsFieldNames, logic.AssetParamsFieldTypes, logic.AssetParamsFieldDocs()) } func appParamsFieldsMarkdown(out io.Writer) { fmt.Fprintf(out, "\n`app_params_get` Fields:\n\n") - fieldTableMarkdown(out, logic.AppParamsFieldNames, logic.AppParamsFieldTypes, logic.AppParamsFieldDocs) + fieldTableMarkdown(out, logic.AppParamsFieldNames, logic.AppParamsFieldTypes, logic.AppParamsFieldDocs()) } func ecDsaCurvesMarkdown(out io.Writer) { @@ -373,11 +373,11 @@ func main() { assetholding.Close() assetparams, _ := os.Create("asset_params_fields.md") - fieldTableMarkdown(assetparams, logic.AssetParamsFieldNames, logic.AssetParamsFieldTypes, logic.AssetParamsFieldDocs) + fieldTableMarkdown(assetparams, logic.AssetParamsFieldNames, logic.AssetParamsFieldTypes, logic.AssetParamsFieldDocs()) assetparams.Close() appparams, _ := os.Create("app_params_fields.md") - fieldTableMarkdown(appparams, logic.AppParamsFieldNames, logic.AppParamsFieldTypes, logic.AppParamsFieldDocs) + fieldTableMarkdown(appparams, logic.AppParamsFieldNames, logic.AppParamsFieldTypes, logic.AppParamsFieldDocs()) appparams.Close() langspecjs, _ := os.Create("langspec.json") diff --git a/data/transactions/logic/README.md b/data/transactions/logic/README.md index 5ce7dd3951..f82dae4cce 100644 --- a/data/transactions/logic/README.md +++ b/data/transactions/logic/README.md @@ -33,7 +33,7 @@ TEAL LogicSigs run in Algorand nodes as part of testing a proposed transaction t If an authorized program executes and finishes with a single non-zero uint64 value on the stack then that program has validated the transaction it is attached to. -The TEAL program has access to data from the transaction it is attached to (`txn` op), any transactions in a transaction group it is part of (`gtxn` op), and a few global values like consensus parameters (`global` op). Some "Args" may be attached to a transaction being validated by a TEAL program. Args are an array of byte strings. A common pattern would be to have the key to unlock some contract as an Arg. Args are recorded on the blockchain and publicly visible when the transaction is submitted to the network. These LogicSig Args are _not_signed. +The TEAL program has access to data from the transaction it is attached to (`txn` op), any transactions in a transaction group it is part of (`gtxn` op), and a few global values like consensus parameters (`global` op). Some "Args" may be attached to a transaction being validated by a TEAL program. Args are an array of byte strings. A common pattern would be to have the key to unlock some contract as an Arg. Args are recorded on the blockchain and publicly visible when the transaction is submitted to the network. These LogicSig Args are _not_ signed. A program can either authorize some delegated action on a normal private key signed or multisig account or be wholly in charge of a contract account. @@ -357,7 +357,7 @@ Asset fields include `AssetHolding` and `AssetParam` fields that are used in the | 8 | AssetReserve | []byte | Reserve address | | 9 | AssetFreeze | []byte | Freeze address | | 10 | AssetClawback | []byte | Clawback address | -| 11 | AssetCreator | []byte | Creator address | +| 11 | AssetCreator | []byte | Creator address. LogicSigVersion >= 5. | **App Fields** @@ -390,8 +390,8 @@ App fields used in the `app_params_get` opcode. | `dup` | duplicate last value on stack | | `dup2` | duplicate two last values on stack: A, B -> A, B, A, B | | `dig n` | push the Nth value from the top of the stack. dig 0 is equivalent to dup | -| `cover n` | remove top of stack, and place it deeper in the stack such that N elements are above it | -| `uncover n` | remove the value at depth N in the stack and shift above items down so the Nth deep value is on top of the stack | +| `cover n` | remove top of stack, and place it deeper in the stack such that N elements are above it. Fails if stack depth <= N. | +| `uncover n` | remove the value at depth N in the stack and shift above items down so the Nth deep value is on top of the stack. Fails if stack depth <= N. | | `swap` | swaps two last values on stack: A, B -> B, A | | `select` | selects one of two values based on top-of-stack: A, B, C -> (if C != 0 then B else A) | | `assert` | immediately fail unless value X is a non-zero number | diff --git a/data/transactions/logic/README_in.md b/data/transactions/logic/README_in.md index 1a90106fe6..ad3b909047 100644 --- a/data/transactions/logic/README_in.md +++ b/data/transactions/logic/README_in.md @@ -33,7 +33,7 @@ TEAL LogicSigs run in Algorand nodes as part of testing a proposed transaction t If an authorized program executes and finishes with a single non-zero uint64 value on the stack then that program has validated the transaction it is attached to. -The TEAL program has access to data from the transaction it is attached to (`txn` op), any transactions in a transaction group it is part of (`gtxn` op), and a few global values like consensus parameters (`global` op). Some "Args" may be attached to a transaction being validated by a TEAL program. Args are an array of byte strings. A common pattern would be to have the key to unlock some contract as an Arg. Args are recorded on the blockchain and publicly visible when the transaction is submitted to the network. These LogicSig Args are _not_signed. +The TEAL program has access to data from the transaction it is attached to (`txn` op), any transactions in a transaction group it is part of (`gtxn` op), and a few global values like consensus parameters (`global` op). Some "Args" may be attached to a transaction being validated by a TEAL program. Args are an array of byte strings. A common pattern would be to have the key to unlock some contract as an Arg. Args are recorded on the blockchain and publicly visible when the transaction is submitted to the network. These LogicSig Args are _not_ signed. A program can either authorize some delegated action on a normal private key signed or multisig account or be wholly in charge of a contract account. diff --git a/data/transactions/logic/TEAL_opcodes.md b/data/transactions/logic/TEAL_opcodes.md index e77ce3b405..367a736cc3 100644 --- a/data/transactions/logic/TEAL_opcodes.md +++ b/data/transactions/logic/TEAL_opcodes.md @@ -737,7 +737,7 @@ See `bnz` for details on how branches work. `b` always jumps to the offset. - Opcode: 0x4e {uint8 depth} - Pops: *... stack*, any - Pushes: any -- remove top of stack, and place it deeper in the stack such that N elements are above it +- remove top of stack, and place it deeper in the stack such that N elements are above it. Fails if stack depth <= N. - LogicSigVersion >= 5 ## uncover n @@ -745,7 +745,7 @@ See `bnz` for details on how branches work. `b` always jumps to the offset. - Opcode: 0x4f {uint8 depth} - Pops: *... stack*, any - Pushes: any -- remove the value at depth N in the stack and shift above items down so the Nth deep value is on top of the stack +- remove the value at depth N in the stack and shift above items down so the Nth deep value is on top of the stack. Fails if stack depth <= N. - LogicSigVersion >= 5 ## concat @@ -1005,7 +1005,7 @@ params: Txn.Accounts offset (or, since v4, an account address that appears in Tx | 8 | AssetReserve | []byte | Reserve address | | 9 | AssetFreeze | []byte | Freeze address | | 10 | AssetClawback | []byte | Clawback address | -| 11 | AssetCreator | []byte | Creator address | +| 11 | AssetCreator | []byte | Creator address. LogicSigVersion >= 5. | params: Before v4, Txn.ForeignAssets offset. Since v4, Txn.ForeignAssets offset or an asset id that appears in Txn.ForeignAssets. Return: did_exist flag (1 if exist and 0 otherwise), value. @@ -1296,7 +1296,7 @@ bitlen interprets arrays as big-endian integers, unlike setbit/getbit - LogicSigVersion >= 5 - Mode: Application -`tx_begin` sets Sender to the application address; Fee to the minimum allowable, taking into account MinTxnFee and credit from overpaying in earlier transactions; and FirstValid/LastValid to the values in the top-level transaction. +`tx_begin` initializes Sender to the application address; Fee to the minimum allowable, taking into account MinTxnFee and credit from overpaying in earlier transactions; FirstValid/LastValid to the values in the top-level transaction, and all other fields to zero values. ## tx_field f @@ -1307,7 +1307,7 @@ bitlen interprets arrays as big-endian integers, unlike setbit/getbit - LogicSigVersion >= 5 - Mode: Application -The following fields may be set by `tx_field` - Sender, Fee, Receiver, Amount, CloseRemainderTo, Type, TypeEnum, XferAsset, AssetAmount, AssetSender, AssetReceiver, AssetCloseTo +`tx_field` fails if X is of the wrong type for F, including a byte array of the wrong size for use as an address when F is an address field. `tx_field` also fails if X is an account or asset that does not appear in `txn.Accounts` or `txn.ForeignAssets` of the top-level transaction. (Setting addresses in asset creation are exempted from this requirement.) ## tx_submit diff --git a/data/transactions/logic/doc.go b/data/transactions/logic/doc.go index 862716a457..b7a5db03b6 100644 --- a/data/transactions/logic/doc.go +++ b/data/transactions/logic/doc.go @@ -109,8 +109,8 @@ var opDocByName = map[string]string{ "dup": "duplicate last value on stack", "dup2": "duplicate two last values on stack: A, B -> A, B, A, B", "dig": "push the Nth value from the top of the stack. dig 0 is equivalent to dup", - "cover": "remove top of stack, and place it deeper in the stack such that N elements are above it", - "uncover": "remove the value at depth N in the stack and shift above items down so the Nth deep value is on top of the stack", + "cover": "remove top of stack, and place it deeper in the stack such that N elements are above it. Fails if stack depth <= N.", + "uncover": "remove the value at depth N in the stack and shift above items down so the Nth deep value is on top of the stack. Fails if stack depth <= N.", "swap": "swaps two last values on stack: A, B -> B, A", "select": "selects one of two values based on top-of-stack: A, B, C -> (if C != 0 then B else A)", "concat": "pop two byte-arrays A and B and join them, push the result", @@ -261,8 +261,8 @@ var opDocExtras = map[string]string{ "asset_params_get": "params: Before v4, Txn.ForeignAssets offset. Since v4, Txn.ForeignAssets offset or an asset id that appears in Txn.ForeignAssets. Return: did_exist flag (1 if exist and 0 otherwise), value.", "app_params_get": "params: Txn.ForeignApps offset or an app id that appears in Txn.ForeignApps. Return: did_exist flag (1 if exist and 0 otherwise), value.", "log": "`log` fails if called more than MaxLogCalls times in a program, or if the sum of logged bytes exceeds 1024 bytes.", - "tx_begin": "`tx_begin` sets Sender to the application address; Fee to the minimum allowable, taking into account MinTxnFee and credit from overpaying in earlier transactions; and FirstValid/LastValid to the values in the top-level transaction.", - "tx_field": "The following fields may be set by `tx_field` - Sender, Fee, Receiver, Amount, CloseRemainderTo, Type, TypeEnum, XferAsset, AssetAmount, AssetSender, AssetReceiver, AssetCloseTo", + "tx_begin": "`tx_begin` initializes Sender to the application address; Fee to the minimum allowable, taking into account MinTxnFee and credit from overpaying in earlier transactions; FirstValid/LastValid to the values in the top-level transaction, and all other fields to zero values.", + "tx_field": "`tx_field` fails if X is of the wrong type for F, including a byte array of the wrong size for use as an address when F is an address field. `tx_field` also fails if X is an account or asset that does not appear in `txn.Accounts` or `txn.ForeignAssets` of the top-level transaction. (Setting addresses in asset creation are exempted from this requirement.)", } // OpDocExtra returns extra documentation text about an op @@ -463,8 +463,8 @@ var AssetHoldingFieldDocs = map[string]string{ "AssetFrozen": "Is the asset frozen or not", } -// AssetParamsFieldDocs are notes on fields available in `asset_params_get` -var AssetParamsFieldDocs = map[string]string{ +// assetParamsFieldDocs are notes on fields available in `asset_params_get` +var assetParamsFieldDocs = map[string]string{ "AssetTotal": "Total number of units of this asset", "AssetDecimals": "See AssetParams.Decimals", "AssetDefaultFrozen": "Frozen by default or not", @@ -479,8 +479,13 @@ var AssetParamsFieldDocs = map[string]string{ "AssetCreator": "Creator address", } -// AppParamsFieldDocs are notes on fields available in `app_params_get` -var AppParamsFieldDocs = map[string]string{ +// AssetParamsFieldDocs are notes on fields available in `asset_params_get` with extra versioning info if any +func AssetParamsFieldDocs() map[string]string { + return fieldsDocWithExtra(assetParamsFieldDocs, assetParamsFieldSpecByName) +} + +// appParamsFieldDocs are notes on fields available in `app_params_get` +var appParamsFieldDocs = map[string]string{ "AppApprovalProgram": "Bytecode of Approval Program", "AppClearStateProgram": "Bytecode of Clear State Program", "AppGlobalNumUint": "Number of uint64 values allowed in Global State", @@ -492,6 +497,11 @@ var AppParamsFieldDocs = map[string]string{ "AppAddress": "Address for which this application has authority", } +// AppParamsFieldDocs are notes on fields available in `app_params_get` with extra versioning info if any +func AppParamsFieldDocs() map[string]string { + return fieldsDocWithExtra(appParamsFieldDocs, appParamsFieldSpecByName) +} + // EcdsaCurveDocs are notes on curves available in `ecdsa_` opcodes var EcdsaCurveDocs = map[string]string{ "Secp256k1": "secp256k1 curve", diff --git a/data/transactions/logic/fields.go b/data/transactions/logic/fields.go index 03a847ea4c..b80de699de 100644 --- a/data/transactions/logic/fields.go +++ b/data/transactions/logic/fields.go @@ -599,7 +599,7 @@ var appParamsFieldSpecByName appNameSpecMap type appNameSpecMap map[string]appParamsFieldSpec func (s appNameSpecMap) getExtraFor(name string) (extra string) { - // Uses 2 here because app fields were introduced in 5 + // Uses 5 here because app fields were introduced in 5 if s[name].version > 5 { extra = fmt.Sprintf("LogicSigVersion >= %d.", s[name].version) } From e0939bc00799637296e3281c4dc4fe83f60f39ff Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Mon, 13 Sep 2021 12:10:11 -0400 Subject: [PATCH 16/28] Require 32 byte hash --- data/transactions/logic/eval.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 62304e9fb4..4bb54f66cc 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -3573,10 +3573,8 @@ func (cx *EvalContext) stackIntoTxnField(sv stackValue, fs txnFieldSpec, txn *tr case ConfigAssetURL: txn.AssetParams.URL, err = sv.string(cx.Proto.MaxAssetURLBytes) case ConfigAssetMetadataHash: - if sv.Bytes == nil { - err = fmt.Errorf("ConfigAssetMetadataHash must be bytes") - } else if len(sv.Bytes) > 32 { - err = fmt.Errorf("ConfigAssetMetadataHash must be <= 32 bytes") + if len(sv.Bytes) != 32 { + err = fmt.Errorf("ConfigAssetMetadataHash must be 32 bytes") } else { copy(txn.AssetParams.MetadataHash[:], sv.Bytes) } From b2b6bc396101d47357e5c72a4f31d00370243511 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Mon, 13 Sep 2021 12:21:48 -0400 Subject: [PATCH 17/28] tx_begin -> itxn_begin for consistency with future `itxn` opcode --- data/transactions/logic/README.md | 6 +- data/transactions/logic/TEAL_opcodes.md | 6 +- data/transactions/logic/assembler_test.go | 18 +- data/transactions/logic/doc.go | 12 +- data/transactions/logic/eval.go | 22 +- data/transactions/logic/evalAppTxn_test.go | 274 +++++++++--------- data/transactions/logic/eval_test.go | 4 +- data/transactions/logic/fields.go | 2 +- data/transactions/logic/opcodes.go | 6 +- ledger/apptxn_test.go | 152 +++++----- .../e2e_subs/tealprogs/app-escrow.teal | 10 +- 11 files changed, 256 insertions(+), 256 deletions(-) diff --git a/data/transactions/logic/README.md b/data/transactions/logic/README.md index 5aed8c26b7..34b32d9999 100644 --- a/data/transactions/logic/README.md +++ b/data/transactions/logic/README.md @@ -197,9 +197,9 @@ The following opcodes allow for the construction and submission of | Op | Description | | --- | --- | -| `tx_begin` | Begin preparation of a new inner transaction | -| `tx_field f` | Set field F of the current inner transaction to X | -| `tx_submit` | Execute the current inner transaction. Panic on any failure. | +| `itxn_begin` | Begin preparation of a new inner transaction | +| `itxn_field f` | Set field F of the current inner transaction to X | +| `itxn_submit` | Execute the current inner transaction. Panic on any failure. | ### Loading Values diff --git a/data/transactions/logic/TEAL_opcodes.md b/data/transactions/logic/TEAL_opcodes.md index c376341bd6..a26033af03 100644 --- a/data/transactions/logic/TEAL_opcodes.md +++ b/data/transactions/logic/TEAL_opcodes.md @@ -1233,7 +1233,7 @@ bitlen interprets arrays as big-endian integers, unlike setbit/getbit `log` can be called up to MaxLogCalls times in a program, and log up to a total of 1k bytes. -## tx_begin +## itxn_begin - Opcode: 0xb1 - Pops: _None_ @@ -1242,7 +1242,7 @@ bitlen interprets arrays as big-endian integers, unlike setbit/getbit - LogicSigVersion >= 5 - Mode: Application -## tx_field f +## itxn_field f - Opcode: 0xb2 {uint8 transaction field index} - Pops: *... stack*, any @@ -1251,7 +1251,7 @@ bitlen interprets arrays as big-endian integers, unlike setbit/getbit - LogicSigVersion >= 5 - Mode: Application -## tx_submit +## itxn_submit - Opcode: 0xb3 - Pops: _None_ diff --git a/data/transactions/logic/assembler_test.go b/data/transactions/logic/assembler_test.go index 0d9aaaf5a8..b720ad660f 100644 --- a/data/transactions/logic/assembler_test.go +++ b/data/transactions/logic/assembler_test.go @@ -312,9 +312,9 @@ extract16bits log txn Nonparticipation gtxn 0 Nonparticipation -tx_begin -tx_field Sender -tx_submit +itxn_begin +itxn_field Sender +itxn_submit int 1 txnas ApplicationArgs int 0 @@ -2250,11 +2250,11 @@ func TestUncoverAsm(t *testing.T) { } func TestTxTypes(t *testing.T) { - testProg(t, "tx_begin; tx_field Sender", 5, expect{2, "tx_field Sender expects 1 stack argument..."}) - testProg(t, "tx_begin; int 1; tx_field Sender", 5, expect{3, "...wanted type []byte got uint64"}) - testProg(t, "tx_begin; byte 0x56127823; tx_field Sender", 5) + testProg(t, "itxn_begin; itxn_field Sender", 5, expect{2, "itxn_field Sender expects 1 stack argument..."}) + testProg(t, "itxn_begin; int 1; itxn_field Sender", 5, expect{3, "...wanted type []byte got uint64"}) + testProg(t, "itxn_begin; byte 0x56127823; itxn_field Sender", 5) - testProg(t, "tx_begin; tx_field Amount", 5, expect{2, "tx_field Amount expects 1 stack argument..."}) - testProg(t, "tx_begin; byte 0x87123376; tx_field Amount", 5, expect{3, "...wanted type uint64 got []byte"}) - testProg(t, "tx_begin; int 1; tx_field Amount", 5) + testProg(t, "itxn_begin; itxn_field Amount", 5, expect{2, "itxn_field Amount expects 1 stack argument..."}) + testProg(t, "itxn_begin; byte 0x87123376; itxn_field Amount", 5, expect{3, "...wanted type uint64 got []byte"}) + testProg(t, "itxn_begin; int 1; itxn_field Amount", 5) } diff --git a/data/transactions/logic/doc.go b/data/transactions/logic/doc.go index d714849d25..2d76426adc 100644 --- a/data/transactions/logic/doc.go +++ b/data/transactions/logic/doc.go @@ -153,10 +153,10 @@ var opDocByName = map[string]string{ "b^": "A bitwise-xor B, where A and B are byte-arrays, zero-left extended to the greater of their lengths", "b~": "X with all bits inverted", - "log": "write bytes to log state of the current application", - "tx_begin": "Begin preparation of a new inner transaction", - "tx_field": "Set field F of the current inner transaction to X", - "tx_submit": "Execute the current inner transaction. Panic on any failure.", + "log": "write bytes to log state of the current application", + "itxn_begin": "Begin preparation of a new inner transaction", + "itxn_field": "Set field F of the current inner transaction to X", + "itxn_submit": "Execute the current inner transaction. Panic on any failure.", "txnas": "push Xth value of the array field F of the current transaction", "gtxnas": "push Xth value of the array field F from the Tth transaction in the current group", @@ -201,7 +201,7 @@ var opcodeImmediateNotes = map[string]string{ "asset_holding_get": "{uint8 asset holding field index}", "asset_params_get": "{uint8 asset params field index}", "app_params_get": "{uint8 app params field index}", - "tx_field": "{uint8 transaction field index}", + "itxn_field": "{uint8 transaction field index}", "txnas": "{uint8 transaction field index}", "gtxnas": "{uint8 transaction group index} {uint8 transaction field index}", "gtxnsas": "{uint8 transaction field index}", @@ -269,7 +269,7 @@ var OpGroups = map[string][]string{ "Loading Values": {"intcblock", "intc", "intc_0", "intc_1", "intc_2", "intc_3", "pushint", "bytecblock", "bytec", "bytec_0", "bytec_1", "bytec_2", "bytec_3", "pushbytes", "bzero", "arg", "arg_0", "arg_1", "arg_2", "arg_3", "txn", "gtxn", "txna", "txnas", "gtxna", "gtxnas", "gtxns", "gtxnsa", "gtxnsas", "global", "load", "loads", "store", "stores", "gload", "gloads", "gaid", "gaids", "args"}, "Flow Control": {"err", "bnz", "bz", "b", "return", "pop", "dup", "dup2", "dig", "cover", "uncover", "swap", "select", "assert", "callsub", "retsub"}, "State Access": {"balance", "min_balance", "app_opted_in", "app_local_get", "app_local_get_ex", "app_global_get", "app_global_get_ex", "app_local_put", "app_global_put", "app_local_del", "app_global_del", "asset_holding_get", "asset_params_get", "app_params_get", "log"}, - "Inner Transactions": {"tx_begin", "tx_field", "tx_submit"}, + "Inner Transactions": {"itxn_begin", "itxn_field", "itxn_submit"}, } // OpCost indicates the cost of an operation over the range of diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 4bb54f66cc..f059a19301 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -348,7 +348,7 @@ type EvalContext struct { version uint64 scratch scratchSpace - subtxn *transactions.SignedTxn // place to build for tx_submit + subtxn *transactions.SignedTxn // place to build for itxn_submit // The transactions Performed() and their effects InnerTxns []transactions.SignedTxnWithAD @@ -3432,7 +3432,7 @@ func authorizedSender(cx *EvalContext, addr basics.Address) bool { func opTxBegin(cx *EvalContext) { if cx.subtxn != nil { - cx.err = errors.New("tx_begin without tx_submit") + cx.err = errors.New("itxn_begin without itxn_submit") return } // Start fresh @@ -3447,8 +3447,8 @@ func opTxBegin(cx *EvalContext) { fee := cx.Proto.MinTxnFee if cx.FeeCredit != nil { // Use credit to shrink the fee, but don't change FeeCredit - // here, because they might never tx_submit, or they might - // change the fee. Do it in tx_submit. + // here, because they might never itxn_submit, or they might + // change the fee. Do it in itxn_submit. fee = basics.SubSaturate(fee, *cx.FeeCredit) } cx.subtxn.Txn.Header = transactions.Header{ @@ -3499,7 +3499,7 @@ func (cx *EvalContext) stackIntoTxnField(sv stackValue, fs txnFieldSpec, txn *tr if ok { txn.Type = txType } else { - err = fmt.Errorf("%s is not a valid Type for tx_field", sv.Bytes) + err = fmt.Errorf("%s is not a valid Type for itxn_field", sv.Bytes) } case TypeEnum: var i uint64 @@ -3513,7 +3513,7 @@ func (cx *EvalContext) stackIntoTxnField(sv stackValue, fs txnFieldSpec, txn *tr if ok { txn.Type = txType } else { - err = fmt.Errorf("%s is not a valid Type for tx_field", TxnTypeNames[i]) + err = fmt.Errorf("%s is not a valid Type for itxn_field", TxnTypeNames[i]) } } else { err = fmt.Errorf("%d is not a valid TypeEnum", i) @@ -3597,21 +3597,21 @@ func (cx *EvalContext) stackIntoTxnField(sv stackValue, fs txnFieldSpec, txn *tr // appl needs to wait. Can't call AVM from AVM. default: - return fmt.Errorf("invalid tx_field %s", fs.field) + return fmt.Errorf("invalid itxn_field %s", fs.field) } return } func opTxField(cx *EvalContext) { if cx.subtxn == nil { - cx.err = errors.New("tx_field without tx_begin") + cx.err = errors.New("itxn_field without itxn_begin") return } last := len(cx.stack) - 1 field := TxnField(cx.program[cx.pc+1]) fs, ok := txnFieldSpecByField[field] if !ok || fs.itxVersion == 0 || fs.itxVersion > cx.version { - cx.err = fmt.Errorf("invalid tx_field field %d", field) + cx.err = fmt.Errorf("invalid itxn_field field %d", field) } sv := cx.stack[last] cx.err = cx.stackIntoTxnField(sv, fs, &cx.subtxn.Txn) @@ -3625,12 +3625,12 @@ func opTxSubmit(cx *EvalContext) { } if cx.subtxn == nil { - cx.err = errors.New("tx_submit without tx_begin") + cx.err = errors.New("itxn_submit without itxn_begin") return } if len(cx.InnerTxns) >= cx.Proto.MaxInnerTransactions { - cx.err = errors.New("tx_submit with MaxInnerTransactions") + cx.err = errors.New("itxn_submit with MaxInnerTransactions") return } diff --git a/data/transactions/logic/evalAppTxn_test.go b/data/transactions/logic/evalAppTxn_test.go index 26d58e62bf..66670f7ffe 100644 --- a/data/transactions/logic/evalAppTxn_test.go +++ b/data/transactions/logic/evalAppTxn_test.go @@ -27,81 +27,81 @@ import ( func TestActionTypes(t *testing.T) { ep, ledger := makeSampleEnv() - testApp(t, "tx_submit; int 1;", ep, "tx_submit without tx_begin") - testApp(t, "int pay; tx_field TypeEnum; tx_submit; int 1;", ep, "tx_field without tx_begin") - testApp(t, "tx_begin; tx_submit; int 1;", ep, "Invalid inner transaction type") + testApp(t, "itxn_submit; int 1;", ep, "itxn_submit without itxn_begin") + testApp(t, "int pay; itxn_field TypeEnum; itxn_submit; int 1;", ep, "itxn_field without itxn_begin") + testApp(t, "itxn_begin; itxn_submit; int 1;", ep, "Invalid inner transaction type") // bad type - testApp(t, "tx_begin; byte \"pya\"; tx_field Type; tx_submit; int 1;", ep, "pya is not a valid Type") + testApp(t, "itxn_begin; byte \"pya\"; itxn_field Type; itxn_submit; int 1;", ep, "pya is not a valid Type") // mixed up the int form for the byte form - testApp(t, obfuscate("tx_begin; int pay; tx_field Type; tx_submit; int 1;"), ep, "Type arg not a byte array") + testApp(t, obfuscate("itxn_begin; int pay; itxn_field Type; itxn_submit; int 1;"), ep, "Type arg not a byte array") // or vice versa - testApp(t, obfuscate("tx_begin; byte \"pay\"; tx_field TypeEnum; tx_submit; int 1;"), ep, "not a uint64") + testApp(t, obfuscate("itxn_begin; byte \"pay\"; itxn_field TypeEnum; itxn_submit; int 1;"), ep, "not a uint64") // good types, not alllowed yet - testApp(t, "tx_begin; byte \"keyreg\"; tx_field Type; tx_submit; int 1;", ep, "keyreg is not a valid Type for tx_field") - testApp(t, "tx_begin; byte \"appl\"; tx_field Type; tx_submit; int 1;", ep, "appl is not a valid Type for tx_field") + testApp(t, "itxn_begin; byte \"keyreg\"; itxn_field Type; itxn_submit; int 1;", ep, "keyreg is not a valid Type for itxn_field") + testApp(t, "itxn_begin; byte \"appl\"; itxn_field Type; itxn_submit; int 1;", ep, "appl is not a valid Type for itxn_field") // same, as enums - testApp(t, "tx_begin; int keyreg; tx_field TypeEnum; tx_submit; int 1;", ep, "keyreg is not a valid Type for tx_field") - testApp(t, "tx_begin; int appl; tx_field TypeEnum; tx_submit; int 1;", ep, "appl is not a valid Type for tx_field") - testApp(t, "tx_begin; int 42; tx_field TypeEnum; tx_submit; int 1;", ep, "42 is not a valid TypeEnum") - testApp(t, "tx_begin; int 0; tx_field TypeEnum; tx_submit; int 1;", ep, "0 is not a valid TypeEnum") + testApp(t, "itxn_begin; int keyreg; itxn_field TypeEnum; itxn_submit; int 1;", ep, "keyreg is not a valid Type for itxn_field") + testApp(t, "itxn_begin; int appl; itxn_field TypeEnum; itxn_submit; int 1;", ep, "appl is not a valid Type for itxn_field") + testApp(t, "itxn_begin; int 42; itxn_field TypeEnum; itxn_submit; int 1;", ep, "42 is not a valid TypeEnum") + testApp(t, "itxn_begin; int 0; itxn_field TypeEnum; itxn_submit; int 1;", ep, "0 is not a valid TypeEnum") // "insufficient balance" because app account is charged fee // (defaults make these 0 pay|axfer to zero address, from app account) - testApp(t, "tx_begin; byte \"pay\"; tx_field Type; tx_submit; int 1;", ep, "insufficient balance") - testApp(t, "tx_begin; byte \"axfer\"; tx_field Type; tx_submit; int 1;", ep, "insufficient balance") - testApp(t, "tx_begin; int pay; tx_field TypeEnum; tx_submit; int 1;", ep, "insufficient balance") - testApp(t, "tx_begin; int axfer; tx_field TypeEnum; tx_submit; int 1;", ep, "insufficient balance") + testApp(t, "itxn_begin; byte \"pay\"; itxn_field Type; itxn_submit; int 1;", ep, "insufficient balance") + testApp(t, "itxn_begin; byte \"axfer\"; itxn_field Type; itxn_submit; int 1;", ep, "insufficient balance") + testApp(t, "itxn_begin; int pay; itxn_field TypeEnum; itxn_submit; int 1;", ep, "insufficient balance") + testApp(t, "itxn_begin; int axfer; itxn_field TypeEnum; itxn_submit; int 1;", ep, "insufficient balance") - testApp(t, "tx_begin; byte \"acfg\"; tx_field Type; tx_submit; int 1;", ep, "insufficient balance") - testApp(t, "tx_begin; byte \"afrz\"; tx_field Type; tx_submit; int 1;", ep, "insufficient balance") - testApp(t, "tx_begin; int acfg; tx_field TypeEnum; tx_submit; int 1;", ep, "insufficient balance") - testApp(t, "tx_begin; int afrz; tx_field TypeEnum; tx_submit; int 1;", ep, "insufficient balance") + testApp(t, "itxn_begin; byte \"acfg\"; itxn_field Type; itxn_submit; int 1;", ep, "insufficient balance") + testApp(t, "itxn_begin; byte \"afrz\"; itxn_field Type; itxn_submit; int 1;", ep, "insufficient balance") + testApp(t, "itxn_begin; int acfg; itxn_field TypeEnum; itxn_submit; int 1;", ep, "insufficient balance") + testApp(t, "itxn_begin; int afrz; itxn_field TypeEnum; itxn_submit; int 1;", ep, "insufficient balance") // Establish 888 as the app id, and fund it. ledger.NewApp(ep.Txn.Txn.Receiver, 888, basics.AppParams{}) ledger.NewAccount(basics.AppIndex(888).Address(), 200000) - testApp(t, "tx_begin; byte \"pay\"; tx_field Type; tx_submit; int 1;", ep) - testApp(t, "tx_begin; int pay; tx_field TypeEnum; tx_submit; int 1;", ep) - // Can't submit because we haven't finished setup, but type passes tx_field - testApp(t, "tx_begin; byte \"axfer\"; tx_field Type; int 1;", ep) - testApp(t, "tx_begin; int axfer; tx_field TypeEnum; int 1;", ep) - testApp(t, "tx_begin; byte \"acfg\"; tx_field Type; int 1;", ep) - testApp(t, "tx_begin; int acfg; tx_field TypeEnum; int 1;", ep) - testApp(t, "tx_begin; byte \"afrz\"; tx_field Type; int 1;", ep) - testApp(t, "tx_begin; int afrz; tx_field TypeEnum; int 1;", ep) + testApp(t, "itxn_begin; byte \"pay\"; itxn_field Type; itxn_submit; int 1;", ep) + testApp(t, "itxn_begin; int pay; itxn_field TypeEnum; itxn_submit; int 1;", ep) + // Can't submit because we haven't finished setup, but type passes itxn_field + testApp(t, "itxn_begin; byte \"axfer\"; itxn_field Type; int 1;", ep) + testApp(t, "itxn_begin; int axfer; itxn_field TypeEnum; int 1;", ep) + testApp(t, "itxn_begin; byte \"acfg\"; itxn_field Type; int 1;", ep) + testApp(t, "itxn_begin; int acfg; itxn_field TypeEnum; int 1;", ep) + testApp(t, "itxn_begin; byte \"afrz\"; itxn_field Type; int 1;", ep) + testApp(t, "itxn_begin; int afrz; itxn_field TypeEnum; int 1;", ep) } func TestFieldTypes(t *testing.T) { ep, _ := makeSampleEnv() - testApp(t, "tx_begin; byte \"pay\"; tx_field Sender;", ep, "not an address") - testApp(t, obfuscate("tx_begin; int 7; tx_field Receiver;"), ep, "not an address") - testApp(t, "tx_begin; byte \"\"; tx_field CloseRemainderTo;", ep, "not an address") - testApp(t, "tx_begin; byte \"\"; tx_field AssetSender;", ep, "not an address") + testApp(t, "itxn_begin; byte \"pay\"; itxn_field Sender;", ep, "not an address") + testApp(t, obfuscate("itxn_begin; int 7; itxn_field Receiver;"), ep, "not an address") + testApp(t, "itxn_begin; byte \"\"; itxn_field CloseRemainderTo;", ep, "not an address") + testApp(t, "itxn_begin; byte \"\"; itxn_field AssetSender;", ep, "not an address") // can't really tell if it's an addres, so 32 bytes gets further - testApp(t, "tx_begin; byte \"01234567890123456789012345678901\"; tx_field AssetReceiver;", + testApp(t, "itxn_begin; byte \"01234567890123456789012345678901\"; itxn_field AssetReceiver;", ep, "invalid Account reference") // but a b32 string rep is not an account - testApp(t, "tx_begin; byte \"GAYTEMZUGU3DOOBZGAYTEMZUGU3DOOBZGAYTEMZUGU3DOOBZGAYZIZD42E\"; tx_field AssetCloseTo;", + testApp(t, "itxn_begin; byte \"GAYTEMZUGU3DOOBZGAYTEMZUGU3DOOBZGAYTEMZUGU3DOOBZGAYZIZD42E\"; itxn_field AssetCloseTo;", ep, "not an address") - testApp(t, obfuscate("tx_begin; byte \"pay\"; tx_field Fee;"), ep, "not a uint64") - testApp(t, obfuscate("tx_begin; byte 0x01; tx_field Amount;"), ep, "not a uint64") - testApp(t, obfuscate("tx_begin; byte 0x01; tx_field XferAsset;"), ep, "not a uint64") - testApp(t, obfuscate("tx_begin; byte 0x01; tx_field AssetAmount;"), ep, "not a uint64") + testApp(t, obfuscate("itxn_begin; byte \"pay\"; itxn_field Fee;"), ep, "not a uint64") + testApp(t, obfuscate("itxn_begin; byte 0x01; itxn_field Amount;"), ep, "not a uint64") + testApp(t, obfuscate("itxn_begin; byte 0x01; itxn_field XferAsset;"), ep, "not a uint64") + testApp(t, obfuscate("itxn_begin; byte 0x01; itxn_field AssetAmount;"), ep, "not a uint64") } func TestAppPay(t *testing.T) { pay := ` - tx_begin - tx_field Amount - tx_field Receiver - tx_field Sender + itxn_begin + itxn_field Amount + itxn_field Receiver + itxn_field Sender int pay - tx_field TypeEnum - tx_submit + itxn_field TypeEnum + itxn_submit int 1 ` @@ -126,10 +126,10 @@ func TestAppPay(t *testing.T) { testApp(t, "txn Receiver; balance; int 100; ==", ep) close := ` - tx_begin - int pay; tx_field TypeEnum - txn Receiver; tx_field CloseRemainderTo - tx_submit + itxn_begin + int pay; itxn_field TypeEnum + txn Receiver; itxn_field CloseRemainderTo + itxn_submit int 1 ` testApp(t, close, ep) @@ -145,24 +145,24 @@ func TestAppAssetOptIn(t *testing.T) { ledger.NewAccount(basics.AppIndex(888).Address(), 200000) axfer := ` -tx_begin -int axfer; tx_field TypeEnum; -int 25; tx_field XferAsset; -int 2; tx_field AssetAmount; -txn Sender; tx_field AssetReceiver; -tx_submit +itxn_begin +int axfer; itxn_field TypeEnum; +int 25; itxn_field XferAsset; +int 2; itxn_field AssetAmount; +txn Sender; itxn_field AssetReceiver; +itxn_submit int 1 ` testApp(t, axfer, ep, "invalid Asset reference") ep.Txn.Txn.ForeignAssets = append(ep.Txn.Txn.ForeignAssets, 25) testApp(t, axfer, ep, "not opted in") // app account not opted in optin := ` -tx_begin -int axfer; tx_field TypeEnum; -int 25; tx_field XferAsset; -int 0; tx_field AssetAmount; -global CurrentApplicationAddress; tx_field AssetReceiver; -tx_submit +itxn_begin +int axfer; itxn_field TypeEnum; +int 25; itxn_field XferAsset; +int 0; itxn_field AssetAmount; +global CurrentApplicationAddress; itxn_field AssetReceiver; +itxn_submit int 1 ` testApp(t, optin, ep, "does not exist") @@ -184,13 +184,13 @@ int 1 testApp(t, "global CurrentApplicationAddress; int 25; asset_holding_get AssetBalance; assert; int 1; ==", ep) close := ` -tx_begin -int axfer; tx_field TypeEnum; -int 25; tx_field XferAsset; -int 0; tx_field AssetAmount; -txn Sender; tx_field AssetReceiver; -txn Sender; tx_field AssetCloseTo; -tx_submit +itxn_begin +int axfer; itxn_field TypeEnum; +int 25; itxn_field XferAsset; +int 0; itxn_field AssetAmount; +txn Sender; itxn_field AssetReceiver; +txn Sender; itxn_field AssetCloseTo; +itxn_submit int 1 ` testApp(t, close, ep) @@ -199,13 +199,13 @@ int 1 func TestRekeyPay(t *testing.T) { pay := ` - tx_begin - tx_field Amount - tx_field Receiver - tx_field Sender + itxn_begin + itxn_field Amount + itxn_field Receiver + itxn_field Sender int pay - tx_field TypeEnum - tx_submit + itxn_field TypeEnum + itxn_submit ` ep, ledger := makeSampleEnv() @@ -222,12 +222,12 @@ func TestRekeyPay(t *testing.T) { func TestDefaultSender(t *testing.T) { pay := ` - tx_begin - tx_field Amount - tx_field Receiver + itxn_begin + itxn_field Amount + itxn_field Receiver int pay - tx_field TypeEnum - tx_submit + itxn_field TypeEnum + itxn_submit ` ep, ledger := makeSampleEnv() @@ -241,15 +241,15 @@ func TestDefaultSender(t *testing.T) { func TestAppAxfer(t *testing.T) { axfer := ` - tx_begin + itxn_begin int 77 - tx_field XferAsset - tx_field AssetAmount - tx_field AssetReceiver - tx_field Sender + itxn_field XferAsset + itxn_field AssetAmount + itxn_field AssetReceiver + itxn_field Sender int axfer - tx_field TypeEnum - tx_submit + itxn_field TypeEnum + itxn_submit ` ep, ledger := makeSampleEnv() @@ -282,13 +282,13 @@ func TestAppAxfer(t *testing.T) { ep.Txn.Txn.ForeignAssets = save noid := ` - tx_begin - tx_field AssetAmount - tx_field AssetReceiver - tx_field Sender + itxn_begin + itxn_field AssetAmount + itxn_field AssetReceiver + itxn_field Sender int axfer - tx_field TypeEnum - tx_submit + itxn_field TypeEnum + itxn_submit ` testApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100"+noid+"int 1", ep, fmt.Sprintf("Sender (%s) not opted in to 0", ledger.ApplicationID().Address())) @@ -302,14 +302,14 @@ func TestAppAxfer(t *testing.T) { func TestExtraFields(t *testing.T) { pay := ` - tx_begin - int 7; tx_field AssetAmount; - tx_field Amount - tx_field Receiver - tx_field Sender + itxn_begin + int 7; itxn_field AssetAmount; + itxn_field Amount + itxn_field Receiver + itxn_field Sender int pay - tx_field TypeEnum - tx_submit + itxn_field TypeEnum + itxn_submit ` ep, ledger := makeSampleEnv() @@ -322,34 +322,34 @@ func TestExtraFields(t *testing.T) { func TestBadField(t *testing.T) { pay := ` - tx_begin - int 7; tx_field AssetAmount; - tx_field Amount - tx_field Receiver - tx_field Sender + itxn_begin + int 7; itxn_field AssetAmount; + itxn_field Amount + itxn_field Receiver + itxn_field Sender int pay - tx_field TypeEnum + itxn_field TypeEnum txn Receiver - tx_field RekeyTo // NOT ALLOWED - tx_submit + itxn_field RekeyTo // NOT ALLOWED + itxn_submit ` ep, ledger := makeSampleEnv() ledger.NewApp(ep.Txn.Txn.Receiver, 888, basics.AppParams{}) testApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100"+pay, ep, - "invalid tx_field RekeyTo") + "invalid itxn_field RekeyTo") } func TestNumInner(t *testing.T) { pay := ` - tx_begin + itxn_begin int 1 - tx_field Amount + itxn_field Amount txn Accounts 1 - tx_field Receiver + itxn_field Receiver int pay - tx_field TypeEnum - tx_submit + itxn_field TypeEnum + itxn_submit ` ep, ledger := makeSampleEnv() @@ -360,25 +360,25 @@ func TestNumInner(t *testing.T) { testApp(t, pay+pay+pay+";int 1", ep) testApp(t, pay+pay+pay+pay+";int 1", ep) // In the sample proto, MaxInnerTransactions = 4 - testApp(t, pay+pay+pay+pay+pay+";int 1", ep, "tx_submit with MaxInnerTransactions") + testApp(t, pay+pay+pay+pay+pay+";int 1", ep, "itxn_submit with MaxInnerTransactions") } func TestAssetCreate(t *testing.T) { create := ` - tx_begin + itxn_begin int acfg - tx_field TypeEnum + itxn_field TypeEnum int 1000000 - tx_field ConfigAssetTotal + itxn_field ConfigAssetTotal int 3 - tx_field ConfigAssetDecimals + itxn_field ConfigAssetDecimals byte "oz" - tx_field ConfigAssetUnitName + itxn_field ConfigAssetUnitName byte "Gold" - tx_field ConfigAssetName + itxn_field ConfigAssetName byte "https://gold.rush/" - tx_field ConfigAssetURL - tx_submit + itxn_field ConfigAssetURL + itxn_submit int 1 ` ep, ledger := makeSampleEnv() @@ -391,15 +391,15 @@ func TestAssetCreate(t *testing.T) { func TestAssetFreeze(t *testing.T) { create := ` - tx_begin - int acfg ; tx_field TypeEnum - int 1000000 ; tx_field ConfigAssetTotal - int 3 ; tx_field ConfigAssetDecimals - byte "oz" ; tx_field ConfigAssetUnitName - byte "Gold" ; tx_field ConfigAssetName - byte "https://gold.rush/" ; tx_field ConfigAssetURL - global CurrentApplicationAddress ; tx_field ConfigAssetFreeze; - tx_submit + itxn_begin + int acfg ; itxn_field TypeEnum + int 1000000 ; itxn_field ConfigAssetTotal + int 3 ; itxn_field ConfigAssetDecimals + byte "oz" ; itxn_field ConfigAssetUnitName + byte "Gold" ; itxn_field ConfigAssetName + byte "https://gold.rush/" ; itxn_field ConfigAssetURL + global CurrentApplicationAddress ; itxn_field ConfigAssetFreeze; + itxn_submit int 1 ` ep, ledger := makeSampleEnv() @@ -409,12 +409,12 @@ func TestAssetFreeze(t *testing.T) { testApp(t, create, ep) freeze := ` - tx_begin - int afrz ; tx_field TypeEnum - int 889 ; tx_field FreezeAsset - txn ApplicationArgs 0; btoi ; tx_field FreezeAssetFrozen - txn Accounts 1 ; tx_field FreezeAssetAccount - tx_submit + itxn_begin + int afrz ; itxn_field TypeEnum + int 889 ; itxn_field FreezeAsset + txn ApplicationArgs 0; btoi ; itxn_field FreezeAssetFrozen + txn Accounts 1 ; itxn_field FreezeAssetAccount + itxn_submit int 1 ` testApp(t, freeze, ep, "invalid Asset reference") diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index 673b2ff473..2cb6cc2552 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -56,7 +56,7 @@ func defaultEvalProtoWithVersion(version uint64) config.ConsensusParams { // These must be identical to keep an old backward compat test working MinTxnFee: 1001, MinBalance: 1001, - // Our sample txn is 42-1066 (and that's used as default in tx_begin) + // Our sample txn is 42-1066 (and that's used as default in itxn_begin) MaxTxnLife: 1500, // Strange choices below so that we test against conflating them AppFlatParamsMinBalance: 1002, @@ -66,7 +66,7 @@ func defaultEvalProtoWithVersion(version uint64) config.ConsensusParams { MaxInnerTransactions: 4, - // With the addition of tx_field, tx_submit, which rely on + // With the addition of itxn_field, itxn_submit, which rely on // machinery outside logic package for validity checking, we // need a more realistic set of consensus paramaters. Asset: true, diff --git a/data/transactions/logic/fields.go b/data/transactions/logic/fields.go index c66538d41a..c08d2b7e83 100644 --- a/data/transactions/logic/fields.go +++ b/data/transactions/logic/fields.go @@ -175,7 +175,7 @@ type txnFieldSpec struct { field TxnField ftype StackType version uint64 // When this field become available to txn/gtxn. 0=always - itxVersion uint64 // When this field become available to tx_field. 0=never + itxVersion uint64 // When this field become available to itxn_field. 0=never } var txnFieldSpecs = []txnFieldSpec{ diff --git a/data/transactions/logic/opcodes.go b/data/transactions/logic/opcodes.go index 8fff33aa03..508070fa07 100644 --- a/data/transactions/logic/opcodes.go +++ b/data/transactions/logic/opcodes.go @@ -301,9 +301,9 @@ var OpSpecs = []OpSpec{ // AVM "effects" {0xb0, "log", opLog, asmDefault, disDefault, oneBytes, nil, 5, runModeApplication, opDefault}, - {0xb1, "tx_begin", opTxBegin, asmDefault, disDefault, nil, nil, 5, runModeApplication, opDefault}, - {0xb2, "tx_field", opTxField, asmTxField, disTxField, oneAny, nil, 5, runModeApplication, stacky(typeTxField, "f")}, - {0xb3, "tx_submit", opTxSubmit, asmDefault, disDefault, nil, nil, 5, runModeApplication, opDefault}, + {0xb1, "itxn_begin", opTxBegin, asmDefault, disDefault, nil, nil, 5, runModeApplication, opDefault}, + {0xb2, "itxn_field", opTxField, asmTxField, disTxField, oneAny, nil, 5, runModeApplication, stacky(typeTxField, "f")}, + {0xb3, "itxn_submit", opTxSubmit, asmDefault, disDefault, nil, nil, 5, runModeApplication, opDefault}, // Dynamically indexing into LogicSigs {0xc0, "txnas", opTxnas, assembleTxnas, disTxn, oneInt, oneAny, 5, modeAny, immediates("f")}, diff --git a/ledger/apptxn_test.go b/ledger/apptxn_test.go index 2683447322..336754ec31 100644 --- a/ledger/apptxn_test.go +++ b/ledger/apptxn_test.go @@ -49,14 +49,14 @@ func TestPayAction(t *testing.T) { Type: "appl", Sender: addrs[0], ApprovalProgram: main(` - tx_begin + itxn_begin int pay - tx_field TypeEnum + itxn_field TypeEnum int 5000 - tx_field Amount + itxn_field Amount txn Accounts 1 - tx_field Receiver - tx_submit + itxn_field Receiver + itxn_submit `), } @@ -200,11 +200,11 @@ func TestAxferAction(t *testing.T) { Type: "appl", Sender: addrs[0], ApprovalProgram: main(` - tx_begin + itxn_begin int axfer - tx_field TypeEnum + itxn_field TypeEnum txn Assets 0 - tx_field XferAsset + itxn_field XferAsset txn ApplicationArgs 0 byte "optin" @@ -212,7 +212,7 @@ func TestAxferAction(t *testing.T) { bz withdraw // let AssetAmount default to 0 global CurrentApplicationAddress - tx_field AssetReceiver + itxn_field AssetReceiver b submit withdraw: txn ApplicationArgs 0 @@ -220,14 +220,14 @@ withdraw: == bz noclose txn Accounts 1 - tx_field AssetCloseTo + itxn_field AssetCloseTo b skipamount noclose: int 10000 - tx_field AssetAmount + itxn_field AssetAmount skipamount: txn Accounts 1 - tx_field AssetReceiver -submit: tx_submit + itxn_field AssetReceiver +submit: itxn_submit `), } @@ -397,24 +397,24 @@ func TestClawbackAction(t *testing.T) { Type: "appl", Sender: addrs[0], ApprovalProgram: main(` - tx_begin + itxn_begin int axfer - tx_field TypeEnum + itxn_field TypeEnum txn Assets 0 - tx_field XferAsset + itxn_field XferAsset txn Accounts 1 - tx_field AssetSender + itxn_field AssetSender txn Accounts 2 - tx_field AssetReceiver + itxn_field AssetReceiver int 1000 - tx_field AssetAmount + itxn_field AssetAmount - tx_submit + itxn_submit `), } @@ -466,23 +466,23 @@ func TestRekeyAction(t *testing.T) { Type: "appl", Sender: addrs[5], ApprovalProgram: main(` - tx_begin + itxn_begin int pay - tx_field TypeEnum + itxn_field TypeEnum int 5000 - tx_field Amount + itxn_field Amount txn Accounts 1 - tx_field Sender + itxn_field Sender txn Accounts 2 - tx_field Receiver + itxn_field Receiver txn NumAccounts int 3 == bz skipclose txn Accounts 3 - tx_field CloseRemainderTo + itxn_field CloseRemainderTo skipclose: - tx_submit + itxn_submit `), } @@ -571,35 +571,35 @@ func TestRekeyActionCloseAccount(t *testing.T) { Sender: addrs[5], ApprovalProgram: main(` // close account 1 - tx_begin + itxn_begin int pay - tx_field TypeEnum + itxn_field TypeEnum txn Accounts 1 - tx_field Sender + itxn_field Sender txn Accounts 2 - tx_field CloseRemainderTo - tx_submit + itxn_field CloseRemainderTo + itxn_submit // reopen account 1 - tx_begin + itxn_begin int pay - tx_field TypeEnum + itxn_field TypeEnum int 5000 - tx_field Amount + itxn_field Amount txn Accounts 1 - tx_field Receiver - tx_submit + itxn_field Receiver + itxn_submit // send from account 1 again (should fail because closing an account erases rekeying) - tx_begin + itxn_begin int pay - tx_field TypeEnum + itxn_field TypeEnum int 1 - tx_field Amount + itxn_field Amount txn Accounts 1 - tx_field Sender + itxn_field Sender txn Accounts 2 - tx_field Receiver - tx_submit + itxn_field Receiver + itxn_submit `), } @@ -645,22 +645,22 @@ func TestDuplicatePayAction(t *testing.T) { Type: "appl", Sender: addrs[0], ApprovalProgram: main(` - tx_begin + itxn_begin int pay - tx_field TypeEnum + itxn_field TypeEnum int 5000 - tx_field Amount + itxn_field Amount txn Accounts 1 - tx_field Receiver - tx_submit - tx_begin + itxn_field Receiver + itxn_submit + itxn_begin int pay - tx_field TypeEnum + itxn_field TypeEnum int 5000 - tx_field Amount + itxn_field Amount txn Accounts 1 - tx_field Receiver - tx_submit + itxn_field Receiver + itxn_submit `), } @@ -719,14 +719,14 @@ func TestInnerTxnCount(t *testing.T) { Type: "appl", Sender: addrs[0], ApprovalProgram: main(` - tx_begin + itxn_begin int pay - tx_field TypeEnum + itxn_field TypeEnum int 5000 - tx_field Amount + itxn_field Amount txn Accounts 1 - tx_field Receiver - tx_submit + itxn_field Receiver + itxn_submit `), } @@ -768,31 +768,31 @@ func TestAcfgAction(t *testing.T) { Type: "appl", Sender: addrs[0], ApprovalProgram: main(` - tx_begin + itxn_begin int acfg - tx_field TypeEnum + itxn_field TypeEnum txn ApplicationArgs 0 byte "create" == bz manager int 1000000 - tx_field ConfigAssetTotal + itxn_field ConfigAssetTotal int 3 - tx_field ConfigAssetDecimals + itxn_field ConfigAssetDecimals byte "oz" - tx_field ConfigAssetUnitName + itxn_field ConfigAssetUnitName byte "Gold" - tx_field ConfigAssetName + itxn_field ConfigAssetName byte "https://gold.rush/" - tx_field ConfigAssetURL + itxn_field ConfigAssetURL global CurrentApplicationAddress dup dup2 - tx_field ConfigAssetManager - tx_field ConfigAssetReserve - tx_field ConfigAssetFreeze - tx_field ConfigAssetClawback + itxn_field ConfigAssetManager + itxn_field ConfigAssetReserve + itxn_field ConfigAssetFreeze + itxn_field ConfigAssetClawback b submit manager: txn ApplicationArgs 0 @@ -813,7 +813,7 @@ clawback: txn ApplicationArgs 0 byte "manager" == -submit: tx_submit +submit: itxn_submit `), } @@ -879,16 +879,16 @@ func TestAsaDuringInit(t *testing.T) { Type: "appl", Sender: addrs[0], ApprovalProgram: ` - tx_begin + itxn_begin int acfg - tx_field TypeEnum + itxn_field TypeEnum int 1000000 - tx_field ConfigAssetTotal + itxn_field ConfigAssetTotal byte "oz" - tx_field ConfigAssetUnitName + itxn_field ConfigAssetUnitName byte "Gold" - tx_field ConfigAssetName - tx_submit + itxn_field ConfigAssetName + itxn_submit int 1 `, } diff --git a/test/scripts/e2e_subs/tealprogs/app-escrow.teal b/test/scripts/e2e_subs/tealprogs/app-escrow.teal index 315f397db6..e742db429b 100644 --- a/test/scripts/e2e_subs/tealprogs/app-escrow.teal +++ b/test/scripts/e2e_subs/tealprogs/app-escrow.teal @@ -126,17 +126,17 @@ not_deposit: - app_local_put - tx_begin + itxn_begin int pay - tx_field TypeEnum + itxn_field TypeEnum txn ApplicationArgs 1 btoi - tx_field Amount + itxn_field Amount txn Sender - tx_field Receiver - tx_submit + itxn_field Receiver + itxn_submit int 1 return From 7c812318efbdb0ebc03f28c11d077b394a609451 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Mon, 13 Sep 2021 14:46:44 -0400 Subject: [PATCH 18/28] Put inner IDs into inner rest response --- daemon/algod/api/server/v2/utils.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/daemon/algod/api/server/v2/utils.go b/daemon/algod/api/server/v2/utils.go index 67c36b61f6..047985b8bc 100644 --- a/daemon/algod/api/server/v2/utils.go +++ b/daemon/algod/api/server/v2/utils.go @@ -319,9 +319,8 @@ func convertTxn(txn *transactions.SignedTxnWithAD) preEncodedTxInfo { response.ReceiverRewards = &txn.ApplyData.ReceiverRewards.Raw response.CloseRewards = &txn.ApplyData.CloseRewards.Raw - // Indexes can't be set until we allow acfg or appl - // response.AssetIndex = computeAssetIndexFromTxn(txn, v2.Node.Ledger()) - // response.ApplicationIndex = computeAppIndexFromTxn(txn, v2.Node.Ledger()) + response.AssetIndex = (*uint64)(&txn.ApplyData.ConfigAsset) + response.ApplicationIndex = (*uint64)(&txn.ApplyData.ApplicationID) // Deltas, Logs, and Inners can not be set until we allow appl // response.LocalStateDelta, response.GlobalStateDelta = convertToDeltas(txn) From 95b2f046645744fa53cb17abf9ae2d8b83f9f9af Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Mon, 13 Sep 2021 12:27:24 -0700 Subject: [PATCH 19/28] Test inner txn indexes --- daemon/algod/api/server/v2/utils.go | 10 ++- test/e2e-go/restAPI/restClient_test.go | 119 +++++++++++++++++++++++++ 2 files changed, 125 insertions(+), 4 deletions(-) diff --git a/daemon/algod/api/server/v2/utils.go b/daemon/algod/api/server/v2/utils.go index 047985b8bc..6498c575c7 100644 --- a/daemon/algod/api/server/v2/utils.go +++ b/daemon/algod/api/server/v2/utils.go @@ -301,12 +301,12 @@ func convertLogs(txn node.TxnWithStatus) *[][]byte { func convertInners(txn *node.TxnWithStatus) *[]preEncodedTxInfo { inner := make([]preEncodedTxInfo, len(txn.ApplyData.EvalDelta.InnerTxns)) for i, itxn := range txn.ApplyData.EvalDelta.InnerTxns { - inner[i] = convertTxn(&itxn) + inner[i] = convertInnerTxn(&itxn) } return &inner } -func convertTxn(txn *transactions.SignedTxnWithAD) preEncodedTxInfo { +func convertInnerTxn(txn *transactions.SignedTxnWithAD) preEncodedTxInfo { // This copies from handlers.PendingTransactionInformation, with // simplifications because we have a SignedTxnWithAD rather than // TxnWithStatus, and we know this txn has committed. @@ -319,8 +319,10 @@ func convertTxn(txn *transactions.SignedTxnWithAD) preEncodedTxInfo { response.ReceiverRewards = &txn.ApplyData.ReceiverRewards.Raw response.CloseRewards = &txn.ApplyData.CloseRewards.Raw - response.AssetIndex = (*uint64)(&txn.ApplyData.ConfigAsset) - response.ApplicationIndex = (*uint64)(&txn.ApplyData.ApplicationID) + // Since this is an inner txn, we know these indexes will be populated. No + // need to search payset for IDs + response.AssetIndex = numOrNil(uint64(txn.ApplyData.ConfigAsset)) + response.ApplicationIndex = numOrNil(uint64(txn.ApplyData.ApplicationID)) // Deltas, Logs, and Inners can not be set until we allow appl // response.LocalStateDelta, response.GlobalStateDelta = convertToDeltas(txn) diff --git a/test/e2e-go/restAPI/restClient_test.go b/test/e2e-go/restAPI/restClient_test.go index ccef6741b3..091385fb54 100644 --- a/test/e2e-go/restAPI/restClient_test.go +++ b/test/e2e-go/restAPI/restClient_test.go @@ -18,6 +18,7 @@ package restapi import ( "context" + "encoding/hex" "errors" "flag" "math" @@ -1030,3 +1031,121 @@ return } } + +func TestPendingTransactionInfoInnerTxnAssetCreate(t *testing.T) { + partitiontest.PartitionTest(t) + + a := require.New(fixtures.SynchronizedTest(t)) + var localFixture fixtures.RestClientFixture + localFixture.Setup(t, filepath.Join("nettemplates", "TwoNodes50EachFuture.json")) + defer localFixture.Shutdown() + + testClient := localFixture.LibGoalClient + + testClient.WaitForRound(1) + + testClient.SetAPIVersionAffinity(algodclient.APIVersionV2, kmdclient.APIVersionV1) + + wh, err := testClient.GetUnencryptedWalletHandle() + a.NoError(err) + addresses, err := testClient.ListAddresses(wh) + a.NoError(err) + _, someAddress := getMaxBalAddr(t, testClient, addresses) + if someAddress == "" { + t.Error("no addr with funds") + } + a.NoError(err) + + prog := `#pragma version 5 +txn ApplicationID +bz end +itxn_begin +int acfg +itxn_field TypeEnum +int 1000000 +itxn_field ConfigAssetTotal +int 3 +itxn_field ConfigAssetDecimals +byte "oz" +itxn_field ConfigAssetUnitName +byte "Gold" +itxn_field ConfigAssetName +byte "https://gold.rush/" +itxn_field ConfigAssetURL +byte 0x67f0cd61653bd34316160bc3f5cd3763c85b114d50d38e1f4e72c3b994411e7b +itxn_field ConfigAssetMetadataHash +itxn_submit +end: +int 1 +return +` + ops, err := logic.AssembleString(prog) + approv := ops.Program + ops, err = logic.AssembleString("#pragma version 5 \nint 1") + clst := ops.Program + + gl := basics.StateSchema{} + lc := basics.StateSchema{} + + // create app + appCreateTxn, err := testClient.MakeUnsignedApplicationCallTx(0, nil, nil, nil, nil, transactions.NoOpOC, approv, clst, gl, lc, 0) + a.NoError(err) + appCreateTxn, err = testClient.FillUnsignedTxTemplate(someAddress, 0, 0, 0, appCreateTxn) + a.NoError(err) + appCreateTxID, err := testClient.SignAndBroadcastTransaction(wh, nil, appCreateTxn) + a.NoError(err) + _, err = waitForTransaction(t, testClient, someAddress, appCreateTxID, 30*time.Second) + a.NoError(err) + + // get app ID + submittedAppCreateTxn, err := testClient.PendingTransactionInformationV2(appCreateTxID) + a.NoError(err) + a.NotNil(submittedAppCreateTxn.ApplicationIndex) + createdAppID := basics.AppIndex(*submittedAppCreateTxn.ApplicationIndex) + a.Greater(uint64(createdAppID), uint64(0)) + + // fund app account + appFundTxn, err := testClient.SendPaymentFromWallet(wh, nil, someAddress, createdAppID.Address().String(), 0, 1_000_000, nil, "", 0, 0) + a.NoError(err) + appFundTxID := appFundTxn.ID() + _, err = waitForTransaction(t, testClient, someAddress, appFundTxID.String(), 30*time.Second) + a.NoError(err) + + // call app, which will issue an ASA create inner txn + appCallTxn, err := testClient.MakeUnsignedAppNoOpTx(uint64(createdAppID), nil, nil, nil, nil) + a.NoError(err) + appCallTxn, err = testClient.FillUnsignedTxTemplate(someAddress, 0, 0, 0, appCallTxn) + a.NoError(err) + appCallTxnTxID, err := testClient.SignAndBroadcastTransaction(wh, nil, appCallTxn) + a.NoError(err) + _, err = waitForTransaction(t, testClient, someAddress, appCallTxnTxID, 30*time.Second) + a.NoError(err) + + // verify pending txn info of outer txn + submittedAppCallTxn, err := testClient.PendingTransactionInformationV2(appCallTxnTxID) + a.NoError(err) + a.Nil(submittedAppCallTxn.ApplicationIndex) + a.Nil(submittedAppCallTxn.AssetIndex) + a.NotNil(submittedAppCallTxn.InnerTxns) + a.Len(*submittedAppCallTxn.InnerTxns, 1) + + // verify pending txn info of inner txn + innerTxn := (*submittedAppCallTxn.InnerTxns)[0] + a.Nil(innerTxn.ApplicationIndex) + a.NotNil(innerTxn.AssetIndex) + createdAssetID := *innerTxn.AssetIndex + a.Greater(createdAssetID, uint64(0)) + + createdAssetInfo, err := testClient.AssetInformationV2(createdAssetID) + a.NoError(err) + a.Equal(createdAssetID, createdAssetInfo.Index) + a.Equal(createdAppID.Address().String(), createdAssetInfo.Params.Creator) + a.Equal(uint64(1000000), createdAssetInfo.Params.Total) + a.Equal(uint64(3), createdAssetInfo.Params.Decimals) + a.Equal("oz", *createdAssetInfo.Params.UnitName) + a.Equal("Gold", *createdAssetInfo.Params.Name) + a.Equal("https://gold.rush/", *createdAssetInfo.Params.Url) + expectedMetadata, err := hex.DecodeString("67f0cd61653bd34316160bc3f5cd3763c85b114d50d38e1f4e72c3b994411e7b") + a.NoError(err) + a.Equal(expectedMetadata, *createdAssetInfo.Params.MetadataHash) +} From 1a3fda368b4e68ef6403f3d076a44a1b4728ce15 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Mon, 13 Sep 2021 20:21:27 -0400 Subject: [PATCH 20/28] Adds an itxn instruction for seeing inner txns (fields and results) --- data/transactions/logic/README.md | 6 + data/transactions/logic/TEAL_opcodes.md | 22 +++ data/transactions/logic/assembler.go | 65 ++++++- data/transactions/logic/assembler_test.go | 7 +- data/transactions/logic/doc.go | 181 +++++++++++-------- data/transactions/logic/eval.go | 116 ++++++++++-- data/transactions/logic/evalAppTxn_test.go | 6 +- data/transactions/logic/evalStateful_test.go | 4 + data/transactions/logic/eval_test.go | 5 +- data/transactions/logic/fields.go | 147 ++++++++------- data/transactions/logic/fields_string.go | 10 +- data/transactions/logic/opcodes.go | 4 +- data/transactions/logictest/ledger.go | 11 +- ledger/apptxn_test.go | 4 +- 14 files changed, 414 insertions(+), 174 deletions(-) diff --git a/data/transactions/logic/README.md b/data/transactions/logic/README.md index 34b32d9999..f49435eefe 100644 --- a/data/transactions/logic/README.md +++ b/data/transactions/logic/README.md @@ -200,6 +200,8 @@ The following opcodes allow for the construction and submission of | `itxn_begin` | Begin preparation of a new inner transaction | | `itxn_field f` | Set field F of the current inner transaction to X | | `itxn_submit` | Execute the current inner transaction. Panic on any failure. | +| `itxn f` | push field F of the last inner transaction to stack | +| `itxna f i` | push Ith valoue of the array field F of the last inner transaction to stack | ### Loading Values @@ -312,6 +314,10 @@ Some of these have immediate data in the byte or bytes after the opcode. | 55 | LocalNumByteSlice | uint64 | Number of local state byteslices in ApplicationCall. LogicSigVersion >= 3. | | 56 | ExtraProgramPages | uint64 | Number of additional pages for each of the application's approval and clear state programs. An ExtraProgramPages of 1 means 2048 more total bytes, or 1024 for each program. LogicSigVersion >= 4. | | 57 | Nonparticipation | uint64 | Marks an account nonparticipating for rewards. LogicSigVersion >= 5. | +| 58 | Logs | []byte | Log messages emmitted by an application call. LogicSigVersion >= 5. | +| 59 | NumLogs | uint64 | Number of Logs. LogicSigVersion >= 5. | +| 60 | EvalConfigAsset | uint64 | Asset ID allocated by the creation of an ASA. LogicSigVersion >= 5. | +| 61 | EvalApplicationID | uint64 | ApplicationID allocated by the creation of an application. LogicSigVersion >= 5. | Additional details in the [opcodes document](TEAL_opcodes.md#txn) on the `txn` op. diff --git a/data/transactions/logic/TEAL_opcodes.md b/data/transactions/logic/TEAL_opcodes.md index a26033af03..713453b008 100644 --- a/data/transactions/logic/TEAL_opcodes.md +++ b/data/transactions/logic/TEAL_opcodes.md @@ -426,6 +426,10 @@ Overflow is an error condition which halts execution and fails the transaction. | 55 | LocalNumByteSlice | uint64 | Number of local state byteslices in ApplicationCall. LogicSigVersion >= 3. | | 56 | ExtraProgramPages | uint64 | Number of additional pages for each of the application's approval and clear state programs. An ExtraProgramPages of 1 means 2048 more total bytes, or 1024 for each program. LogicSigVersion >= 4. | | 57 | Nonparticipation | uint64 | Marks an account nonparticipating for rewards. LogicSigVersion >= 5. | +| 58 | Logs | []byte | Log messages emmitted by an application call. LogicSigVersion >= 5. | +| 59 | NumLogs | uint64 | Number of Logs. LogicSigVersion >= 5. | +| 60 | EvalConfigAsset | uint64 | Asset ID allocated by the creation of an ASA. LogicSigVersion >= 5. | +| 61 | EvalApplicationID | uint64 | ApplicationID allocated by the creation of an application. LogicSigVersion >= 5. | TypeEnum mapping: @@ -1260,6 +1264,24 @@ bitlen interprets arrays as big-endian integers, unlike setbit/getbit - LogicSigVersion >= 5 - Mode: Application +## itxn f + +- Opcode: 0xb4 {uint8 transaction field index} +- Pops: _None_ +- Pushes: any +- push field F of the last inner transaction to stack +- LogicSigVersion >= 5 +- Mode: Application + +## itxna f i + +- Opcode: 0xb5 {uint8 transaction field index} {uint8 transaction field array index} +- Pops: _None_ +- Pushes: any +- push Ith valoue of the array field F of the last inner transaction to stack +- LogicSigVersion >= 5 +- Mode: Application + ## txnas f - Opcode: 0xc0 {uint8 transaction field index} diff --git a/data/transactions/logic/assembler.go b/data/transactions/logic/assembler.go index 516dfdd33f..6b31b63aa8 100644 --- a/data/transactions/logic/assembler.go +++ b/data/transactions/logic/assembler.go @@ -1055,6 +1055,69 @@ func assembleGtxnsas(ops *OpStream, spec *OpSpec, args []string) error { return nil } +// asmItxn delegates to asmItxnInternal or asmItxna depending on number of operands +func asmItxn(ops *OpStream, spec *OpSpec, args []string) error { + if len(args) == 1 { + return asmItxnOnly(ops, spec, args) + } + if len(args) == 2 { + itxna := OpsByName[ops.Version]["itxna"] + return asmItxna(ops, &itxna, args) + } + return ops.errorf("%s expects one or two arguments", spec.Name) +} + +func asmItxnOnly(ops *OpStream, spec *OpSpec, args []string) error { + if len(args) != 1 { + return ops.errorf("%s expects one argument", spec.Name) + } + fs, ok := txnFieldSpecByName[args[0]] + if !ok { + return ops.errorf("%s unknown field: %#v", spec.Name, args[0]) + } + _, ok = txnaFieldSpecByField[fs.field] + if ok { + return ops.errorf("found array field %#v in %s op", args[0], spec.Name) + } + if fs.version > ops.Version { + return ops.errorf("field %#v available in version %d. Missed #pragma version?", args[0], fs.version) + } + ops.pending.WriteByte(spec.Opcode) + ops.pending.WriteByte(uint8(fs.field)) + ops.returns(fs.ftype) + return nil +} + +func asmItxna(ops *OpStream, spec *OpSpec, args []string) error { + if len(args) != 2 { + return ops.errorf("%s expects two immediate arguments", spec.Name) + } + fs, ok := txnFieldSpecByName[args[0]] + if !ok { + return ops.errorf("%s unknown field: %#v", spec.Name, args[0]) + } + _, ok = txnaFieldSpecByField[fs.field] + if !ok { + return ops.errorf("%s unknown field: %#v", spec.Name, args[0]) + } + if fs.version > ops.Version { + return ops.errorf("%s %#v available in version %d. Missed #pragma version?", spec.Name, args[0], fs.version) + } + arrayFieldIdx, err := strconv.ParseUint(args[1], 0, 64) + if err != nil { + return ops.error(err) + } + if arrayFieldIdx > 255 { + return ops.errorf("%s array index beyond 255: %d", spec.Name, arrayFieldIdx) + } + + ops.pending.WriteByte(spec.Opcode) + ops.pending.WriteByte(uint8(fs.field)) + ops.pending.WriteByte(uint8(arrayFieldIdx)) + ops.returns(fs.ftype) + return nil +} + func assembleGlobal(ops *OpStream, spec *OpSpec, args []string) error { if len(args) != 1 { return ops.errorf("%s expects one argument", spec.Name) @@ -2396,7 +2459,7 @@ func checkPushBytes(cx *EvalContext) error { return cx.err } -// This is also used to disassemble gtxns, gtxnsas and txnas +// This is also used to disassemble gtxns, gtxnsas, txnas, itxn func disTxn(dis *disassembleState, spec *OpSpec) (string, error) { lastIdx := dis.pc + 1 if len(dis.program) <= lastIdx { diff --git a/data/transactions/logic/assembler_test.go b/data/transactions/logic/assembler_test.go index b720ad660f..3e9c4bef36 100644 --- a/data/transactions/logic/assembler_test.go +++ b/data/transactions/logic/assembler_test.go @@ -328,6 +328,7 @@ int 0 loads int 0 stores +itxna Logs 3 ` var nonsense = map[uint64]string{ @@ -343,7 +344,7 @@ var compiled = map[uint64]string{ 2: "022008b7a60cf8acd19181cf959a12f8acd19181cf951af8acd19181cf15f8acd191810f01020026050212340c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d024242047465737400320032013202320328292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e0102222324252104082209240a220b230c240d250e230f23102311231223132314181b1c2b171615400003290349483403350222231d4a484848482a50512a63222352410003420000432105602105612105270463484821052b62482b642b65484821052b2106662b21056721072b682b692107210570004848210771004848361c0037001a0031183119311b311d311e311f3120210721051e312131223123312431253126312731283129312a312b312c312d312e312f", 3: "032008b7a60cf8acd19181cf959a12f8acd19181cf951af8acd19181cf15f8acd191810f01020026050212340c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d024242047465737400320032013202320328292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e0102222324252104082209240a220b230c240d250e230f23102311231223132314181b1c2b171615400003290349483403350222231d4a484848482a50512a63222352410003420000432105602105612105270463484821052b62482b642b65484821052b2106662b21056721072b682b692107210570004848210771004848361c0037001a0031183119311b311d311e311f3120210721051e312131223123312431253126312731283129312a312b312c312d312e312f4478222105531421055427042106552105082106564c4d4b02210538212106391c0081e80780046a6f686e", 4: "042004010200b7a60c26040242420c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f23102311231223132314181b1c28171615400003290349483403350222231d4a484848482a50512a632223524100034200004322602261222b634848222862482864286548482228236628226724286828692422700048482471004848361c0037001a0031183119311b311d311e311f312024221e312131223123312431253126312731283129312a312b312c312d312e312f44782522531422542b2355220823564c4d4b0222382123391c0081e80780046a6f686e2281d00f24231f880003420001892223902291922394239593a0a1a2a3a4a5a6a7a8a9aaabacadae23af3a00003b003c003d8164", - 5: "052004010002b7a60c26040242420c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f23102311231223132314181b1c28171615400003290349483403350222231d4a484848482a50512a632223524100034200004322602261222b634848222862482864286548482228246628226723286828692322700048482371004848361c0037001a0031183119311b311d311e311f312023221e312131223123312431253126312731283129312a312b312c312d312e312f44782522531422542b2455220824564c4d4b0222382124391c0081e80780046a6f686e2281d00f23241f880003420001892224902291922494249593a0a1a2a3a4a5a6a7a8a9aaabacadae24af3a00003b003c003d816472064e014f0180070123456789abcd57000823810858235b235a2359b03139330039b1b200b322c01a23c1001a2323c21a23c3233e233f}", + 5: "052004010002b7a60c26040242420c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f23102311231223132314181b1c28171615400003290349483403350222231d4a484848482a50512a632223524100034200004322602261222b634848222862482864286548482228246628226723286828692322700048482371004848361c0037001a0031183119311b311d311e311f312023221e312131223123312431253126312731283129312a312b312c312d312e312f44782522531422542b2455220824564c4d4b0222382124391c0081e80780046a6f686e2281d00f23241f880003420001892224902291922494249593a0a1a2a3a4a5a6a7a8a9aaabacadae24af3a00003b003c003d816472064e014f0180070123456789abcd57000823810858235b235a2359b03139330039b1b200b322c01a23c1001a2323c21a23c3233e233fb53a03", } func pseudoOp(opcode string) bool { @@ -1334,6 +1335,10 @@ gtxn 12 Fee txn ExtraProgramPages txn Nonparticipation global CurrentApplicationAddress +txna Logs 1 +txn NumLogs +txn EvalConfigAsset +txn EvalApplicationID `, AssemblerMaxVersion) for _, globalField := range GlobalFieldNames { if !strings.Contains(text, globalField) { diff --git a/data/transactions/logic/doc.go b/data/transactions/logic/doc.go index 2d76426adc..368a342562 100644 --- a/data/transactions/logic/doc.go +++ b/data/transactions/logic/doc.go @@ -86,6 +86,8 @@ var opDocByName = map[string]string{ "gtxna": "push Ith value of the array field F from the Tth transaction in the current group", "gtxnsa": "push Ith value of the array field F from the Xth transaction in the current group", "global": "push value from globals to stack", + "itxn": "push field F of the last inner transaction to stack", + "itxna": "push Ith valoue of the array field F of the last inner transaction to stack", "load": "copy a value from scratch space to the stack", "store": "pop value X. store X to the Ith scratch space", "loads": "copy a value from the Xth scratch space to the stack", @@ -170,41 +172,50 @@ func OpDoc(opName string) string { } var opcodeImmediateNotes = map[string]string{ - "intcblock": "{varuint length} [{varuint value}, ...]", - "intc": "{uint8 int constant index}", - "pushint": "{varuint int}", - "bytecblock": "{varuint length} [({varuint value length} bytes), ...]", - "bytec": "{uint8 byte constant index}", - "pushbytes": "{varuint length} {bytes}", - "arg": "{uint8 arg index N}", - "txn": "{uint8 transaction field index}", - "gtxn": "{uint8 transaction group index} {uint8 transaction field index}", - "gtxns": "{uint8 transaction field index}", - "txna": "{uint8 transaction field index} {uint8 transaction field array index}", - "gtxna": "{uint8 transaction group index} {uint8 transaction field index} {uint8 transaction field array index}", - "gtxnsa": "{uint8 transaction field index} {uint8 transaction field array index}", - "global": "{uint8 global field index}", - "bnz": "{int16 branch offset, big endian}", - "bz": "{int16 branch offset, big endian}", - "b": "{int16 branch offset, big endian}", - "callsub": "{int16 branch offset, big endian}", - "load": "{uint8 position in scratch space to load from}", - "store": "{uint8 position in scratch space to store to}", - "gload": "{uint8 transaction group index} {uint8 position in scratch space to load from}", - "gloads": "{uint8 position in scratch space to load from}", - "gaid": "{uint8 transaction group index}", - "substring": "{uint8 start position} {uint8 end position}", - "extract": "{uint8 start position} {uint8 length}", - "dig": "{uint8 depth}", - "cover": "{uint8 depth}", - "uncover": "{uint8 depth}", + "intcblock": "{varuint length} [{varuint value}, ...]", + "intc": "{uint8 int constant index}", + "pushint": "{varuint int}", + "bytecblock": "{varuint length} [({varuint value length} bytes), ...]", + "bytec": "{uint8 byte constant index}", + "pushbytes": "{varuint length} {bytes}", + + "arg": "{uint8 arg index N}", + "global": "{uint8 global field index}", + + "txn": "{uint8 transaction field index}", + "gtxn": "{uint8 transaction group index} {uint8 transaction field index}", + "gtxns": "{uint8 transaction field index}", + "txna": "{uint8 transaction field index} {uint8 transaction field array index}", + "gtxna": "{uint8 transaction group index} {uint8 transaction field index} {uint8 transaction field array index}", + "gtxnsa": "{uint8 transaction field index} {uint8 transaction field array index}", + "txnas": "{uint8 transaction field index}", + "gtxnas": "{uint8 transaction group index} {uint8 transaction field index}", + "gtxnsas": "{uint8 transaction field index}", + + "bnz": "{int16 branch offset, big endian}", + "bz": "{int16 branch offset, big endian}", + "b": "{int16 branch offset, big endian}", + "callsub": "{int16 branch offset, big endian}", + + "load": "{uint8 position in scratch space to load from}", + "store": "{uint8 position in scratch space to store to}", + "gload": "{uint8 transaction group index} {uint8 position in scratch space to load from}", + "gloads": "{uint8 position in scratch space to load from}", + "gaid": "{uint8 transaction group index}", + + "substring": "{uint8 start position} {uint8 end position}", + "extract": "{uint8 start position} {uint8 length}", + "dig": "{uint8 depth}", + "cover": "{uint8 depth}", + "uncover": "{uint8 depth}", + "asset_holding_get": "{uint8 asset holding field index}", "asset_params_get": "{uint8 asset params field index}", "app_params_get": "{uint8 app params field index}", - "itxn_field": "{uint8 transaction field index}", - "txnas": "{uint8 transaction field index}", - "gtxnas": "{uint8 transaction group index} {uint8 transaction field index}", - "gtxnsas": "{uint8 transaction field index}", + + "itxn_field": "{uint8 transaction field index}", + "itxn": "{uint8 transaction field index}", + "itxna": "{uint8 transaction field index} {uint8 transaction field array index}", } // OpImmediateNote returns a short string about immediate data which follows the op byte @@ -269,7 +280,7 @@ var OpGroups = map[string][]string{ "Loading Values": {"intcblock", "intc", "intc_0", "intc_1", "intc_2", "intc_3", "pushint", "bytecblock", "bytec", "bytec_0", "bytec_1", "bytec_2", "bytec_3", "pushbytes", "bzero", "arg", "arg_0", "arg_1", "arg_2", "arg_3", "txn", "gtxn", "txna", "txnas", "gtxna", "gtxnas", "gtxns", "gtxnsa", "gtxnsas", "global", "load", "loads", "store", "stores", "gload", "gloads", "gaid", "gaids", "args"}, "Flow Control": {"err", "bnz", "bz", "b", "return", "pop", "dup", "dup2", "dig", "cover", "uncover", "swap", "select", "assert", "callsub", "retsub"}, "State Access": {"balance", "min_balance", "app_opted_in", "app_local_get", "app_local_get_ex", "app_global_get", "app_global_get_ex", "app_local_put", "app_global_put", "app_local_del", "app_global_del", "asset_holding_get", "asset_params_get", "app_params_get", "log"}, - "Inner Transactions": {"itxn_begin", "itxn_field", "itxn_submit"}, + "Inner Transactions": {"itxn_begin", "itxn_field", "itxn_submit", "itxn", "itxna"}, } // OpCost indicates the cost of an operation over the range of @@ -337,48 +348,55 @@ func OnCompletionDescription(value uint64) string { const OnCompletionPreamble = "An application transaction must indicate the action to be taken following the execution of its approvalProgram or clearStateProgram. The constants below describe the available actions." var txnFieldDocs = map[string]string{ - "Sender": "32 byte address", - "Fee": "micro-Algos", - "FirstValid": "round number", - "FirstValidTime": "Causes program to fail; reserved for future use", - "LastValid": "round number", - "Note": "Any data up to 1024 bytes", - "Lease": "32 byte lease value", - "Receiver": "32 byte address", - "Amount": "micro-Algos", - "CloseRemainderTo": "32 byte address", - "VotePK": "32 byte address", - "SelectionPK": "32 byte address", - "VoteFirst": "The first round that the participation key is valid.", - "VoteLast": "The last round that the participation key is valid.", - "VoteKeyDilution": "Dilution for the 2-level participation key", - "Nonparticipation": "Marks an account nonparticipating for rewards", - "Type": "Transaction type as bytes", - "TypeEnum": "See table below", - "XferAsset": "Asset ID", - "AssetAmount": "value in Asset's units", - "AssetSender": "32 byte address. Causes clawback of all value of asset from AssetSender if Sender is the Clawback address of the asset.", - "AssetReceiver": "32 byte address", - "AssetCloseTo": "32 byte address", - "GroupIndex": "Position of this transaction within an atomic transaction group. A stand-alone transaction is implicitly element 0 in a group of 1", - "TxID": "The computed ID for this transaction. 32 bytes.", - "ApplicationID": "ApplicationID from ApplicationCall transaction", - "OnCompletion": "ApplicationCall transaction on completion action", - "ApplicationArgs": "Arguments passed to the application in the ApplicationCall transaction", - "NumAppArgs": "Number of ApplicationArgs", - "Accounts": "Accounts listed in the ApplicationCall transaction", - "NumAccounts": "Number of Accounts", - "Assets": "Foreign Assets listed in the ApplicationCall transaction", - "NumAssets": "Number of Assets", - "Applications": "Foreign Apps listed in the ApplicationCall transaction", - "NumApplications": "Number of Applications", - "GlobalNumUint": "Number of global state integers in ApplicationCall", - "GlobalNumByteSlice": "Number of global state byteslices in ApplicationCall", - "LocalNumUint": "Number of local state integers in ApplicationCall", - "LocalNumByteSlice": "Number of local state byteslices in ApplicationCall", - "ApprovalProgram": "Approval program", - "ClearStateProgram": "Clear state program", - "RekeyTo": "32 byte Sender's new AuthAddr", + "Type": "Transaction type as bytes", + "TypeEnum": "See table below", + "Sender": "32 byte address", + "Fee": "micro-Algos", + "FirstValid": "round number", + "FirstValidTime": "Causes program to fail; reserved for future use", + "LastValid": "round number", + "Note": "Any data up to 1024 bytes", + "Lease": "32 byte lease value", + "RekeyTo": "32 byte Sender's new AuthAddr", + + "GroupIndex": "Position of this transaction within an atomic transaction group. A stand-alone transaction is implicitly element 0 in a group of 1", + "TxID": "The computed ID for this transaction. 32 bytes.", + + "Receiver": "32 byte address", + "Amount": "micro-Algos", + "CloseRemainderTo": "32 byte address", + + "VotePK": "32 byte address", + "SelectionPK": "32 byte address", + "VoteFirst": "The first round that the participation key is valid.", + "VoteLast": "The last round that the participation key is valid.", + "VoteKeyDilution": "Dilution for the 2-level participation key", + "Nonparticipation": "Marks an account nonparticipating for rewards", + + "XferAsset": "Asset ID", + "AssetAmount": "value in Asset's units", + "AssetSender": "32 byte address. Causes clawback of all value of asset from AssetSender if Sender is the Clawback address of the asset.", + "AssetReceiver": "32 byte address", + "AssetCloseTo": "32 byte address", + + "ApplicationID": "ApplicationID from ApplicationCall transaction", + "OnCompletion": "ApplicationCall transaction on completion action", + "ApplicationArgs": "Arguments passed to the application in the ApplicationCall transaction", + "NumAppArgs": "Number of ApplicationArgs", + "Accounts": "Accounts listed in the ApplicationCall transaction", + "NumAccounts": "Number of Accounts", + "Assets": "Foreign Assets listed in the ApplicationCall transaction", + "NumAssets": "Number of Assets", + "Applications": "Foreign Apps listed in the ApplicationCall transaction", + "NumApplications": "Number of Applications", + "GlobalNumUint": "Number of global state integers in ApplicationCall", + "GlobalNumByteSlice": "Number of global state byteslices in ApplicationCall", + "LocalNumUint": "Number of local state integers in ApplicationCall", + "LocalNumByteSlice": "Number of local state byteslices in ApplicationCall", + "ApprovalProgram": "Approval program", + "ClearStateProgram": "Clear state program", + "ExtraProgramPages": "Number of additional pages for each of the application's approval and clear state programs. An ExtraProgramPages of 1 means 2048 more total bytes, or 1024 for each program.", + "ConfigAsset": "Asset ID in asset config transaction", "ConfigAssetTotal": "Total number of units of this asset created", "ConfigAssetDecimals": "Number of digits to display after the decimal place when displaying the asset", @@ -391,10 +409,15 @@ var txnFieldDocs = map[string]string{ "ConfigAssetReserve": "32 byte address", "ConfigAssetFreeze": "32 byte address", "ConfigAssetClawback": "32 byte address", - "FreezeAsset": "Asset ID being frozen or un-frozen", - "FreezeAssetAccount": "32 byte address of the account whose asset slot is being frozen or un-frozen", - "FreezeAssetFrozen": "The new frozen value, 0 or 1", - "ExtraProgramPages": "Number of additional pages for each of the application's approval and clear state programs. An ExtraProgramPages of 1 means 2048 more total bytes, or 1024 for each program.", + + "FreezeAsset": "Asset ID being frozen or un-frozen", + "FreezeAssetAccount": "32 byte address of the account whose asset slot is being frozen or un-frozen", + "FreezeAssetFrozen": "The new frozen value, 0 or 1", + + "Logs": "Log messages emmitted by an application call", + "NumLogs": "Number of Logs", + "EvalConfigAsset": "Asset ID allocated by the creation of an ASA", + "EvalApplicationID": "ApplicationID allocated by the creation of an application", } // TxnFieldDocs are notes on fields available by `txn` and `gtxn` with extra versioning info if any diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index f059a19301..847fe76769 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -1883,7 +1883,8 @@ func TxnFieldToTealValue(txn *transactions.Transaction, groupIndex int, field Tx return basics.TealValue{}, fmt.Errorf("negative groupIndex %d", groupIndex) } cx := EvalContext{EvalParams: EvalParams{GroupIndex: uint64(groupIndex)}} - sv, err := cx.txnFieldToStack(txn, field, arrayFieldIdx, uint64(groupIndex)) + fs := txnFieldSpecByField[field] + sv, err := cx.txnFieldToStack(txn, fs, arrayFieldIdx, uint64(groupIndex)) return sv.toTealValue(), err } @@ -1903,9 +1904,38 @@ func (cx *EvalContext) getTxID(txn *transactions.Transaction, groupIndex uint64) return txid } -func (cx *EvalContext) txnFieldToStack(txn *transactions.Transaction, field TxnField, arrayFieldIdx uint64, groupIndex uint64) (sv stackValue, err error) { +func (cx *EvalContext) itxnFieldToStack(itxn *transactions.SignedTxnWithAD, fs txnFieldSpec, arrayFieldIdx uint64) (sv stackValue, err error) { + if fs.effects { + switch fs.field { + case Logs: + if arrayFieldIdx >= uint64(len(itxn.EvalDelta.Logs)) { + err = fmt.Errorf("invalid Logs index %d", arrayFieldIdx) + return + } + sv.Bytes = nilToEmpty([]byte(itxn.EvalDelta.Logs[arrayFieldIdx])) + case NumLogs: + sv.Uint = uint64(len(itxn.EvalDelta.Logs)) + case EvalConfigAsset: + sv.Uint = uint64(itxn.ApplyData.ConfigAsset) + case EvalApplicationID: + sv.Uint = uint64(itxn.ApplyData.ApplicationID) + } + } else { + if fs.field == GroupIndex { + err = errors.New("inner txn has no GroupIndex") + } else { + sv, err = cx.txnFieldToStack(&itxn.Txn, fs, arrayFieldIdx, 0) + } + } + return +} + +func (cx *EvalContext) txnFieldToStack(txn *transactions.Transaction, fs txnFieldSpec, arrayFieldIdx uint64, groupIndex uint64) (sv stackValue, err error) { + if fs.effects { + return sv, errors.New("Unable to obtain effects from top-level transactions") + } err = nil - switch field { + switch fs.field { case Sender: sv.Bytes = txn.Sender[:] case Fee: @@ -2055,14 +2085,13 @@ func (cx *EvalContext) txnFieldToStack(txn *transactions.Transaction, field TxnF case ExtraProgramPages: sv.Uint = uint64(txn.ExtraProgramPages) default: - err = fmt.Errorf("invalid txn field %d", field) + err = fmt.Errorf("invalid txn field %d", fs.field) return } - txnField := TxnField(field) - txnFieldType := TxnFieldTypes[txnField] + txnFieldType := TxnFieldTypes[fs.field] if !typecheck(txnFieldType, sv.argType()) { - err = fmt.Errorf("%s expected field type is %s but got %s", txnField.String(), txnFieldType.String(), sv.argType().String()) + err = fmt.Errorf("%s expected field type is %s but got %s", fs.field.String(), txnFieldType.String(), sv.argType().String()) } return } @@ -2079,7 +2108,7 @@ func opTxn(cx *EvalContext) { cx.err = fmt.Errorf("invalid txn field %d", field) return } - sv, err := cx.txnFieldToStack(&cx.Txn.Txn, field, 0, cx.GroupIndex) + sv, err := cx.txnFieldToStack(&cx.Txn.Txn, fs, 0, cx.GroupIndex) if err != nil { cx.err = err return @@ -2100,7 +2129,7 @@ func opTxna(cx *EvalContext) { return } arrayFieldIdx := uint64(cx.program[cx.pc+2]) - sv, err := cx.txnFieldToStack(&cx.Txn.Txn, field, arrayFieldIdx, cx.GroupIndex) + sv, err := cx.txnFieldToStack(&cx.Txn.Txn, fs, arrayFieldIdx, cx.GroupIndex) if err != nil { cx.err = err return @@ -2123,7 +2152,7 @@ func opTxnas(cx *EvalContext) { return } arrayFieldIdx := cx.stack[last].Uint - sv, err := cx.txnFieldToStack(&cx.Txn.Txn, field, arrayFieldIdx, cx.GroupIndex) + sv, err := cx.txnFieldToStack(&cx.Txn.Txn, fs, arrayFieldIdx, cx.GroupIndex) if err != nil { cx.err = err return @@ -2155,7 +2184,7 @@ func opGtxn(cx *EvalContext) { // GroupIndex; asking this when we just specified it is _dumb_, but oh well sv.Uint = uint64(gtxid) } else { - sv, err = cx.txnFieldToStack(tx, field, 0, uint64(gtxid)) + sv, err = cx.txnFieldToStack(tx, fs, 0, uint64(gtxid)) if err != nil { cx.err = err return @@ -2183,7 +2212,7 @@ func opGtxna(cx *EvalContext) { return } arrayFieldIdx := uint64(cx.program[cx.pc+3]) - sv, err := cx.txnFieldToStack(tx, field, arrayFieldIdx, uint64(gtxid)) + sv, err := cx.txnFieldToStack(tx, fs, arrayFieldIdx, uint64(gtxid)) if err != nil { cx.err = err return @@ -2212,7 +2241,7 @@ func opGtxnas(cx *EvalContext) { return } arrayFieldIdx := cx.stack[last].Uint - sv, err := cx.txnFieldToStack(tx, field, arrayFieldIdx, uint64(gtxid)) + sv, err := cx.txnFieldToStack(tx, fs, arrayFieldIdx, uint64(gtxid)) if err != nil { cx.err = err return @@ -2245,7 +2274,7 @@ func opGtxns(cx *EvalContext) { // GroupIndex; asking this when we just specified it is _dumb_, but oh well sv.Uint = gtxid } else { - sv, err = cx.txnFieldToStack(tx, field, 0, gtxid) + sv, err = cx.txnFieldToStack(tx, fs, 0, gtxid) if err != nil { cx.err = err return @@ -2274,7 +2303,7 @@ func opGtxnsa(cx *EvalContext) { return } arrayFieldIdx := uint64(cx.program[cx.pc+2]) - sv, err := cx.txnFieldToStack(tx, field, arrayFieldIdx, gtxid) + sv, err := cx.txnFieldToStack(tx, fs, arrayFieldIdx, gtxid) if err != nil { cx.err = err return @@ -2304,7 +2333,7 @@ func opGtxnsas(cx *EvalContext) { return } arrayFieldIdx := cx.stack[last].Uint - sv, err := cx.txnFieldToStack(tx, field, arrayFieldIdx, gtxid) + sv, err := cx.txnFieldToStack(tx, fs, arrayFieldIdx, gtxid) if err != nil { cx.err = err return @@ -2313,6 +2342,61 @@ func opGtxnsas(cx *EvalContext) { cx.stack = cx.stack[:last] } +func opItxn(cx *EvalContext) { + field := TxnField(cx.program[cx.pc+1]) + fs, ok := txnFieldSpecByField[field] + if !ok || fs.version > cx.version { + cx.err = fmt.Errorf("invalid itxn field %d", field) + return + } + _, ok = txnaFieldSpecByField[field] + if ok { + cx.err = fmt.Errorf("invalid itxn field %d", field) + return + } + + if len(cx.InnerTxns) == 0 { + cx.err = fmt.Errorf("no inner transaction available %d", field) + return + } + + itxn := &cx.InnerTxns[len(cx.InnerTxns)-1] + sv, err := cx.itxnFieldToStack(itxn, fs, 0) + if err != nil { + cx.err = err + return + } + cx.stack = append(cx.stack, sv) +} + +func opItxna(cx *EvalContext) { + field := TxnField(cx.program[cx.pc+1]) + fs, ok := txnFieldSpecByField[field] + if !ok || fs.version > cx.version { + cx.err = fmt.Errorf("invalid itxn field %d", field) + return + } + _, ok = txnaFieldSpecByField[field] + if !ok { + cx.err = fmt.Errorf("itxna unsupported field %d", field) + return + } + arrayFieldIdx := uint64(cx.program[cx.pc+2]) + + if len(cx.InnerTxns) == 0 { + cx.err = fmt.Errorf("no inner transaction available %d", field) + return + } + + itxn := &cx.InnerTxns[len(cx.InnerTxns)-1] + sv, err := cx.itxnFieldToStack(itxn, fs, arrayFieldIdx) + if err != nil { + cx.err = err + return + } + cx.stack = append(cx.stack, sv) +} + func opGaidImpl(cx *EvalContext, groupIdx uint64, opName string) (sv stackValue, err error) { if groupIdx >= uint64(len(cx.TxnGroup)) { err = fmt.Errorf("%s lookup TxnGroup[%d] but it only has %d", opName, groupIdx, len(cx.TxnGroup)) diff --git a/data/transactions/logic/evalAppTxn_test.go b/data/transactions/logic/evalAppTxn_test.go index 66670f7ffe..80bf0c01dd 100644 --- a/data/transactions/logic/evalAppTxn_test.go +++ b/data/transactions/logic/evalAppTxn_test.go @@ -397,10 +397,12 @@ func TestAssetFreeze(t *testing.T) { int 3 ; itxn_field ConfigAssetDecimals byte "oz" ; itxn_field ConfigAssetUnitName byte "Gold" ; itxn_field ConfigAssetName - byte "https://gold.rush/" ; itxn_field ConfigAssetURL + byte "https://gold.rush/" ; itxn_field ConfigAssetURL global CurrentApplicationAddress ; itxn_field ConfigAssetFreeze; itxn_submit - int 1 + itxn EvalConfigAsset + int 889 + == ` ep, ledger := makeSampleEnv() ledger.NewApp(ep.Txn.Txn.Receiver, 888, basics.AppParams{}) diff --git a/data/transactions/logic/evalStateful_test.go b/data/transactions/logic/evalStateful_test.go index 2e020c2647..ca8630df86 100644 --- a/data/transactions/logic/evalStateful_test.go +++ b/data/transactions/logic/evalStateful_test.go @@ -2347,6 +2347,7 @@ func TestReturnTypes(t *testing.T) { require.NoError(t, err) algoValue := basics.TealValue{Type: basics.TealUintType, Uint: 0x77} ledger.NewLocal(txn.Txn.Receiver, 1, string(key), algoValue) + ledger.NewAccount(basics.AppIndex(1).Address(), 1000000) ep.Ledger = ledger @@ -2389,6 +2390,9 @@ func TestReturnTypes(t *testing.T) { "gtxnas": "gtxnas 0 ApplicationArgs", "gtxnsas": "pop; pop; int 0; int 0; gtxnsas ApplicationArgs", "args": "args", + "itxn": "itxn_begin; int pay; itxn_field TypeEnum; itxn_submit; itxn EvalConfigAsset", + // This next one is a cop out. Can't use itxna Logs until we have inner appl + "itxna": "itxn_begin; int pay; itxn_field TypeEnum; itxn_submit; itxn NumLogs", } byName := OpsByName[LogicVersion] diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index 2cb6cc2552..46fa0c08e8 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -1525,8 +1525,9 @@ func TestTxn(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - for _, txnField := range TxnFieldNames { - if !strings.Contains(testTxnProgramTextV5, txnField) { + for i, txnField := range TxnFieldNames { + fs := txnFieldSpecByField[TxnField(i)] + if !fs.effects && !strings.Contains(testTxnProgramTextV5, txnField) { if txnField != FirstValidTime.String() { t.Errorf("TestTxn missing field %v", txnField) } diff --git a/data/transactions/logic/fields.go b/data/transactions/logic/fields.go index c08d2b7e83..051611255e 100644 --- a/data/transactions/logic/fields.go +++ b/data/transactions/logic/fields.go @@ -149,6 +149,18 @@ const ( // Nonparticipation Transaction.Nonparticipation Nonparticipation + // Logs Transaction.ApplyData.EvalDelta.Logs + Logs + + // NumLogs len(Logs) + NumLogs + + // EvalConfigAsset Transaction.ApplyData.EvalDelta.ConfigAsset + EvalConfigAsset + + // EvalApplicationID Transaction.ApplyData.EvalDelta.ApplicationID + EvalApplicationID + invalidTxnField // fence for some setup that loops from Sender..invalidTxnField ) @@ -176,72 +188,78 @@ type txnFieldSpec struct { ftype StackType version uint64 // When this field become available to txn/gtxn. 0=always itxVersion uint64 // When this field become available to itxn_field. 0=never + effects bool // Is this a field on the "effects"? That is, something in ApplyData } var txnFieldSpecs = []txnFieldSpec{ - {Sender, StackBytes, 0, 5}, - {Fee, StackUint64, 0, 5}, - {FirstValid, StackUint64, 0, 0}, - {FirstValidTime, StackUint64, 0, 0}, - {LastValid, StackUint64, 0, 0}, - {Note, StackBytes, 0, 0}, - {Lease, StackBytes, 0, 0}, - {Receiver, StackBytes, 0, 5}, - {Amount, StackUint64, 0, 5}, - {CloseRemainderTo, StackBytes, 0, 5}, - {VotePK, StackBytes, 0, 0}, - {SelectionPK, StackBytes, 0, 0}, - {VoteFirst, StackUint64, 0, 0}, - {VoteLast, StackUint64, 0, 0}, - {VoteKeyDilution, StackUint64, 0, 0}, - {Type, StackBytes, 0, 5}, - {TypeEnum, StackUint64, 0, 5}, - {XferAsset, StackUint64, 0, 5}, - {AssetAmount, StackUint64, 0, 5}, - {AssetSender, StackBytes, 0, 5}, - {AssetReceiver, StackBytes, 0, 5}, - {AssetCloseTo, StackBytes, 0, 5}, - {GroupIndex, StackUint64, 0, 0}, - {TxID, StackBytes, 0, 0}, - {ApplicationID, StackUint64, 2, 0}, - {OnCompletion, StackUint64, 2, 0}, - {ApplicationArgs, StackBytes, 2, 0}, - {NumAppArgs, StackUint64, 2, 0}, - {Accounts, StackBytes, 2, 0}, - {NumAccounts, StackUint64, 2, 0}, - {ApprovalProgram, StackBytes, 2, 0}, - {ClearStateProgram, StackBytes, 2, 0}, - {RekeyTo, StackBytes, 2, 0}, - {ConfigAsset, StackUint64, 2, 5}, - {ConfigAssetTotal, StackUint64, 2, 5}, - {ConfigAssetDecimals, StackUint64, 2, 5}, - {ConfigAssetDefaultFrozen, StackUint64, 2, 5}, - {ConfigAssetUnitName, StackBytes, 2, 5}, - {ConfigAssetName, StackBytes, 2, 5}, - {ConfigAssetURL, StackBytes, 2, 5}, - {ConfigAssetMetadataHash, StackBytes, 2, 5}, - {ConfigAssetManager, StackBytes, 2, 5}, - {ConfigAssetReserve, StackBytes, 2, 5}, - {ConfigAssetFreeze, StackBytes, 2, 5}, - {ConfigAssetClawback, StackBytes, 2, 5}, - {FreezeAsset, StackUint64, 2, 5}, - {FreezeAssetAccount, StackBytes, 2, 5}, - {FreezeAssetFrozen, StackUint64, 2, 5}, - {Assets, StackUint64, 3, 0}, - {NumAssets, StackUint64, 3, 0}, - {Applications, StackUint64, 3, 0}, - {NumApplications, StackUint64, 3, 0}, - {GlobalNumUint, StackUint64, 3, 0}, - {GlobalNumByteSlice, StackUint64, 3, 0}, - {LocalNumUint, StackUint64, 3, 0}, - {LocalNumByteSlice, StackUint64, 3, 0}, - {ExtraProgramPages, StackUint64, 4, 0}, - {Nonparticipation, StackUint64, 5, 0}, + {Sender, StackBytes, 0, 5, false}, + {Fee, StackUint64, 0, 5, false}, + {FirstValid, StackUint64, 0, 0, false}, + {FirstValidTime, StackUint64, 0, 0, false}, + {LastValid, StackUint64, 0, 0, false}, + {Note, StackBytes, 0, 0, false}, + {Lease, StackBytes, 0, 0, false}, + {Receiver, StackBytes, 0, 5, false}, + {Amount, StackUint64, 0, 5, false}, + {CloseRemainderTo, StackBytes, 0, 5, false}, + {VotePK, StackBytes, 0, 0, false}, + {SelectionPK, StackBytes, 0, 0, false}, + {VoteFirst, StackUint64, 0, 0, false}, + {VoteLast, StackUint64, 0, 0, false}, + {VoteKeyDilution, StackUint64, 0, 0, false}, + {Type, StackBytes, 0, 5, false}, + {TypeEnum, StackUint64, 0, 5, false}, + {XferAsset, StackUint64, 0, 5, false}, + {AssetAmount, StackUint64, 0, 5, false}, + {AssetSender, StackBytes, 0, 5, false}, + {AssetReceiver, StackBytes, 0, 5, false}, + {AssetCloseTo, StackBytes, 0, 5, false}, + {GroupIndex, StackUint64, 0, 0, false}, + {TxID, StackBytes, 0, 0, false}, + {ApplicationID, StackUint64, 2, 0, false}, + {OnCompletion, StackUint64, 2, 0, false}, + {ApplicationArgs, StackBytes, 2, 0, false}, + {NumAppArgs, StackUint64, 2, 0, false}, + {Accounts, StackBytes, 2, 0, false}, + {NumAccounts, StackUint64, 2, 0, false}, + {ApprovalProgram, StackBytes, 2, 0, false}, + {ClearStateProgram, StackBytes, 2, 0, false}, + {RekeyTo, StackBytes, 2, 0, false}, + {ConfigAsset, StackUint64, 2, 5, false}, + {ConfigAssetTotal, StackUint64, 2, 5, false}, + {ConfigAssetDecimals, StackUint64, 2, 5, false}, + {ConfigAssetDefaultFrozen, StackUint64, 2, 5, false}, + {ConfigAssetUnitName, StackBytes, 2, 5, false}, + {ConfigAssetName, StackBytes, 2, 5, false}, + {ConfigAssetURL, StackBytes, 2, 5, false}, + {ConfigAssetMetadataHash, StackBytes, 2, 5, false}, + {ConfigAssetManager, StackBytes, 2, 5, false}, + {ConfigAssetReserve, StackBytes, 2, 5, false}, + {ConfigAssetFreeze, StackBytes, 2, 5, false}, + {ConfigAssetClawback, StackBytes, 2, 5, false}, + {FreezeAsset, StackUint64, 2, 5, false}, + {FreezeAssetAccount, StackBytes, 2, 5, false}, + {FreezeAssetFrozen, StackUint64, 2, 5, false}, + {Assets, StackUint64, 3, 0, false}, + {NumAssets, StackUint64, 3, 0, false}, + {Applications, StackUint64, 3, 0, false}, + {NumApplications, StackUint64, 3, 0, false}, + {GlobalNumUint, StackUint64, 3, 0, false}, + {GlobalNumByteSlice, StackUint64, 3, 0, false}, + {LocalNumUint, StackUint64, 3, 0, false}, + {LocalNumByteSlice, StackUint64, 3, 0, false}, + {ExtraProgramPages, StackUint64, 4, 0, false}, + {Nonparticipation, StackUint64, 5, 0, false}, + + {Logs, StackBytes, 5, 5, true}, + {NumLogs, StackUint64, 5, 5, true}, + {EvalConfigAsset, StackUint64, 5, 5, true}, + {EvalApplicationID, StackUint64, 5, 5, true}, } // TxnaFieldNames are arguments to the 'txna' opcode // It is a subset of txn transaction fields so initialized here in-place -var TxnaFieldNames = []string{ApplicationArgs.String(), Accounts.String(), Assets.String(), Applications.String()} +var TxnaFieldNames = []string{ApplicationArgs.String(), Accounts.String(), Assets.String(), Applications.String(), Logs.String()} // TxnaFieldTypes is StackBytes or StackUint64 parallel to TxnaFieldNames var TxnaFieldTypes = []StackType{ @@ -249,13 +267,16 @@ var TxnaFieldTypes = []StackType{ txnaFieldSpecByField[Accounts].ftype, txnaFieldSpecByField[Assets].ftype, txnaFieldSpecByField[Applications].ftype, + txnaFieldSpecByField[Logs].ftype, } var txnaFieldSpecByField = map[TxnField]txnFieldSpec{ - ApplicationArgs: {ApplicationArgs, StackBytes, 2, 0}, - Accounts: {Accounts, StackBytes, 2, 0}, - Assets: {Assets, StackUint64, 3, 0}, - Applications: {Applications, StackUint64, 3, 0}, + ApplicationArgs: {ApplicationArgs, StackBytes, 2, 0, false}, + Accounts: {Accounts, StackBytes, 2, 0, false}, + Assets: {Assets, StackUint64, 3, 0, false}, + Applications: {Applications, StackUint64, 3, 0, false}, + + Logs: {Logs, StackBytes, 5, 5, true}, } var innerTxnTypes = map[string]protocol.TxType{ diff --git a/data/transactions/logic/fields_string.go b/data/transactions/logic/fields_string.go index f41e831e67..22d4ef0136 100644 --- a/data/transactions/logic/fields_string.go +++ b/data/transactions/logic/fields_string.go @@ -66,12 +66,16 @@ func _() { _ = x[LocalNumByteSlice-55] _ = x[ExtraProgramPages-56] _ = x[Nonparticipation-57] - _ = x[invalidTxnField-58] + _ = x[Logs-58] + _ = x[NumLogs-59] + _ = x[EvalConfigAsset-60] + _ = x[EvalApplicationID-61] + _ = x[invalidTxnField-62] } -const _TxnField_name = "SenderFeeFirstValidFirstValidTimeLastValidNoteLeaseReceiverAmountCloseRemainderToVotePKSelectionPKVoteFirstVoteLastVoteKeyDilutionTypeTypeEnumXferAssetAssetAmountAssetSenderAssetReceiverAssetCloseToGroupIndexTxIDApplicationIDOnCompletionApplicationArgsNumAppArgsAccountsNumAccountsApprovalProgramClearStateProgramRekeyToConfigAssetConfigAssetTotalConfigAssetDecimalsConfigAssetDefaultFrozenConfigAssetUnitNameConfigAssetNameConfigAssetURLConfigAssetMetadataHashConfigAssetManagerConfigAssetReserveConfigAssetFreezeConfigAssetClawbackFreezeAssetFreezeAssetAccountFreezeAssetFrozenAssetsNumAssetsApplicationsNumApplicationsGlobalNumUintGlobalNumByteSliceLocalNumUintLocalNumByteSliceExtraProgramPagesNonparticipationinvalidTxnField" +const _TxnField_name = "SenderFeeFirstValidFirstValidTimeLastValidNoteLeaseReceiverAmountCloseRemainderToVotePKSelectionPKVoteFirstVoteLastVoteKeyDilutionTypeTypeEnumXferAssetAssetAmountAssetSenderAssetReceiverAssetCloseToGroupIndexTxIDApplicationIDOnCompletionApplicationArgsNumAppArgsAccountsNumAccountsApprovalProgramClearStateProgramRekeyToConfigAssetConfigAssetTotalConfigAssetDecimalsConfigAssetDefaultFrozenConfigAssetUnitNameConfigAssetNameConfigAssetURLConfigAssetMetadataHashConfigAssetManagerConfigAssetReserveConfigAssetFreezeConfigAssetClawbackFreezeAssetFreezeAssetAccountFreezeAssetFrozenAssetsNumAssetsApplicationsNumApplicationsGlobalNumUintGlobalNumByteSliceLocalNumUintLocalNumByteSliceExtraProgramPagesNonparticipationLogsNumLogsEvalConfigAssetEvalApplicationIDinvalidTxnField" -var _TxnField_index = [...]uint16{0, 6, 9, 19, 33, 42, 46, 51, 59, 65, 81, 87, 98, 107, 115, 130, 134, 142, 151, 162, 173, 186, 198, 208, 212, 225, 237, 252, 262, 270, 281, 296, 313, 320, 331, 347, 366, 390, 409, 424, 438, 461, 479, 497, 514, 533, 544, 562, 579, 585, 594, 606, 621, 634, 652, 664, 681, 698, 714, 729} +var _TxnField_index = [...]uint16{0, 6, 9, 19, 33, 42, 46, 51, 59, 65, 81, 87, 98, 107, 115, 130, 134, 142, 151, 162, 173, 186, 198, 208, 212, 225, 237, 252, 262, 270, 281, 296, 313, 320, 331, 347, 366, 390, 409, 424, 438, 461, 479, 497, 514, 533, 544, 562, 579, 585, 594, 606, 621, 634, 652, 664, 681, 698, 714, 718, 725, 740, 757, 772} func (i TxnField) String() string { if i < 0 || i >= TxnField(len(_TxnField_index)-1) { diff --git a/data/transactions/logic/opcodes.go b/data/transactions/logic/opcodes.go index 508070fa07..232601e662 100644 --- a/data/transactions/logic/opcodes.go +++ b/data/transactions/logic/opcodes.go @@ -304,8 +304,10 @@ var OpSpecs = []OpSpec{ {0xb1, "itxn_begin", opTxBegin, asmDefault, disDefault, nil, nil, 5, runModeApplication, opDefault}, {0xb2, "itxn_field", opTxField, asmTxField, disTxField, oneAny, nil, 5, runModeApplication, stacky(typeTxField, "f")}, {0xb3, "itxn_submit", opTxSubmit, asmDefault, disDefault, nil, nil, 5, runModeApplication, opDefault}, + {0xb4, "itxn", opItxn, asmItxn, disTxn, nil, oneAny, 5, runModeApplication, immediates("f")}, + {0xb5, "itxna", opItxna, asmItxna, disTxna, nil, oneAny, 5, runModeApplication, immediates("f", "i")}, - // Dynamically indexing into LogicSigs + // Dynamic indexing {0xc0, "txnas", opTxnas, assembleTxnas, disTxn, oneInt, oneAny, 5, modeAny, immediates("f")}, {0xc1, "gtxnas", opGtxnas, assembleGtxnas, disGtxn, oneInt, oneAny, 5, modeAny, immediates("t", "f")}, {0xc2, "gtxnsas", opGtxnsas, assembleGtxnsas, disTxn, twoInts, oneAny, 5, modeAny, immediates("f")}, diff --git a/data/transactions/logictest/ledger.go b/data/transactions/logictest/ledger.go index b31b0b30a4..5d75b210fc 100644 --- a/data/transactions/logictest/ledger.go +++ b/data/transactions/logictest/ledger.go @@ -649,10 +649,11 @@ func (l *Ledger) axfer(from basics.Address, xfer transactions.AssetTransferTxnFi return nil } -func (l *Ledger) acfg(from basics.Address, cfg transactions.AssetConfigTxnFields) error { +func (l *Ledger) acfg(from basics.Address, cfg transactions.AssetConfigTxnFields) (transactions.ApplyData, error) { if cfg.ConfigAsset == 0 { - l.NewAsset(from, basics.AssetIndex(l.freshID()), cfg.AssetParams) - return nil + aid := basics.AssetIndex(l.freshID()) + l.NewAsset(from, aid, cfg.AssetParams) + return transactions.ApplyData{ConfigAsset: aid}, nil } // This is just a mock. We don't check all the rules about // not setting fields that have been zeroed. Nor do we keep @@ -661,7 +662,7 @@ func (l *Ledger) acfg(from basics.Address, cfg transactions.AssetConfigTxnFields Creator: from, AssetParams: cfg.AssetParams, } - return nil + return transactions.ApplyData{}, nil } func (l *Ledger) afrz(from basics.Address, frz transactions.AssetFreezeTxnFields) error { @@ -711,7 +712,7 @@ func (l *Ledger) Perform(txn *transactions.Transaction, spec transactions.Specia case protocol.AssetTransferTx: err = l.axfer(txn.Sender, txn.AssetTransferTxnFields) case protocol.AssetConfigTx: - err = l.acfg(txn.Sender, txn.AssetConfigTxnFields) + ad, err = l.acfg(txn.Sender, txn.AssetConfigTxnFields) case protocol.AssetFreezeTx: err = l.afrz(txn.Sender, txn.AssetFreezeTxnFields) default: diff --git a/ledger/apptxn_test.go b/ledger/apptxn_test.go index 336754ec31..dd9df52a20 100644 --- a/ledger/apptxn_test.go +++ b/ledger/apptxn_test.go @@ -889,7 +889,9 @@ func TestAsaDuringInit(t *testing.T) { byte "Gold" itxn_field ConfigAssetName itxn_submit - int 1 + itxn EvalConfigAsset + int 3 + assert `, } From 69081efaceef9732759a8ddd3d592fae25a85088 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Mon, 13 Sep 2021 20:39:23 -0400 Subject: [PATCH 21/28] spelling --- data/transactions/logic/README.md | 2 +- data/transactions/logic/TEAL_opcodes.md | 2 +- data/transactions/logic/doc.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/data/transactions/logic/README.md b/data/transactions/logic/README.md index fac63ae82b..6d81af5db9 100644 --- a/data/transactions/logic/README.md +++ b/data/transactions/logic/README.md @@ -310,7 +310,7 @@ Some of these have immediate data in the byte or bytes after the opcode. | 55 | LocalNumByteSlice | uint64 | Number of local state byteslices in ApplicationCall. LogicSigVersion >= 3. | | 56 | ExtraProgramPages | uint64 | Number of additional pages for each of the application's approval and clear state programs. An ExtraProgramPages of 1 means 2048 more total bytes, or 1024 for each program. LogicSigVersion >= 4. | | 57 | Nonparticipation | uint64 | Marks an account nonparticipating for rewards. LogicSigVersion >= 5. | -| 58 | Logs | []byte | Log messages emmitted by an application call. LogicSigVersion >= 5. | +| 58 | Logs | []byte | Log messages emitted by an application call. LogicSigVersion >= 5. | | 59 | NumLogs | uint64 | Number of Logs. LogicSigVersion >= 5. | | 60 | EvalConfigAsset | uint64 | Asset ID allocated by the creation of an ASA. LogicSigVersion >= 5. | | 61 | EvalApplicationID | uint64 | ApplicationID allocated by the creation of an application. LogicSigVersion >= 5. | diff --git a/data/transactions/logic/TEAL_opcodes.md b/data/transactions/logic/TEAL_opcodes.md index 2a5a0d6e94..79ca0b3032 100644 --- a/data/transactions/logic/TEAL_opcodes.md +++ b/data/transactions/logic/TEAL_opcodes.md @@ -480,7 +480,7 @@ Overflow is an error condition which halts execution and fails the transaction. | 55 | LocalNumByteSlice | uint64 | Number of local state byteslices in ApplicationCall. LogicSigVersion >= 3. | | 56 | ExtraProgramPages | uint64 | Number of additional pages for each of the application's approval and clear state programs. An ExtraProgramPages of 1 means 2048 more total bytes, or 1024 for each program. LogicSigVersion >= 4. | | 57 | Nonparticipation | uint64 | Marks an account nonparticipating for rewards. LogicSigVersion >= 5. | -| 58 | Logs | []byte | Log messages emmitted by an application call. LogicSigVersion >= 5. | +| 58 | Logs | []byte | Log messages emitted by an application call. LogicSigVersion >= 5. | | 59 | NumLogs | uint64 | Number of Logs. LogicSigVersion >= 5. | | 60 | EvalConfigAsset | uint64 | Asset ID allocated by the creation of an ASA. LogicSigVersion >= 5. | | 61 | EvalApplicationID | uint64 | ApplicationID allocated by the creation of an application. LogicSigVersion >= 5. | diff --git a/data/transactions/logic/doc.go b/data/transactions/logic/doc.go index 3812aacde6..71d38e53d1 100644 --- a/data/transactions/logic/doc.go +++ b/data/transactions/logic/doc.go @@ -433,7 +433,7 @@ var txnFieldDocs = map[string]string{ "FreezeAssetAccount": "32 byte address of the account whose asset slot is being frozen or un-frozen", "FreezeAssetFrozen": "The new frozen value, 0 or 1", - "Logs": "Log messages emmitted by an application call", + "Logs": "Log messages emitted by an application call", "NumLogs": "Number of Logs", "EvalConfigAsset": "Asset ID allocated by the creation of an ASA", "EvalApplicationID": "ApplicationID allocated by the creation of an application", From 0658d4c3a5bf9b4fff4e51a0fff7ac3c61085cbd Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Mon, 13 Sep 2021 20:54:58 -0400 Subject: [PATCH 22/28] Fix doc name, unit test to avoid problems --- data/transactions/logic/doc.go | 6 +++--- data/transactions/logic/doc_test.go | 11 +++++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/data/transactions/logic/doc.go b/data/transactions/logic/doc.go index 71d38e53d1..746138614c 100644 --- a/data/transactions/logic/doc.go +++ b/data/transactions/logic/doc.go @@ -171,7 +171,7 @@ var opDocByName = map[string]string{ "log": "write bytes to log state of the current application", "itxn_begin": "Begin preparation of a new inner transaction", "itxn_field": "Set field F of the current inner transaction to X", - "itxn_submit": "Execute the current inner transaction. Panic on any failure.", + "itxn_submit": "Execute the current inner transaction. Fail if the transaction can not be applied.", } // OpDoc returns a description of the op @@ -279,8 +279,8 @@ var opDocExtras = map[string]string{ "asset_params_get": "params: Before v4, Txn.ForeignAssets offset. Since v4, Txn.ForeignAssets offset or an asset id that appears in Txn.ForeignAssets. Return: did_exist flag (1 if exist and 0 otherwise), value.", "app_params_get": "params: Txn.ForeignApps offset or an app id that appears in Txn.ForeignApps. Return: did_exist flag (1 if exist and 0 otherwise), value.", "log": "`log` fails if called more than MaxLogCalls times in a program, or if the sum of logged bytes exceeds 1024 bytes.", - "tx_begin": "`tx_begin` initializes Sender to the application address; Fee to the minimum allowable, taking into account MinTxnFee and credit from overpaying in earlier transactions; FirstValid/LastValid to the values in the top-level transaction, and all other fields to zero values.", - "tx_field": "`tx_field` fails if X is of the wrong type for F, including a byte array of the wrong size for use as an address when F is an address field. `tx_field` also fails if X is an account or asset that does not appear in `txn.Accounts` or `txn.ForeignAssets` of the top-level transaction. (Setting addresses in asset creation are exempted from this requirement.)", + "itxn_begin": "`itxn_begin` initializes Sender to the application address; Fee to the minimum allowable, taking into account MinTxnFee and credit from overpaying in earlier transactions; FirstValid/LastValid to the values in the top-level transaction, and all other fields to zero values.", + "itxn_field": "`itxn_field` fails if X is of the wrong type for F, including a byte array of the wrong size for use as an address when F is an address field. `tx_field` also fails if X is an account or asset that does not appear in `txn.Accounts` or `txn.ForeignAssets` of the top-level transaction. (Setting addresses in asset creation are exempted from this requirement.)", } // OpDocExtra returns extra documentation text about an op diff --git a/data/transactions/logic/doc_test.go b/data/transactions/logic/doc_test.go index f1804524b8..96d51d13b7 100644 --- a/data/transactions/logic/doc_test.go +++ b/data/transactions/logic/doc_test.go @@ -54,6 +54,17 @@ func TestOpDocs(t *testing.T) { require.Len(t, EcdsaCurveDocs, len(EcdsaCurveNames)) } +func TestDocStragglers(t *testing.T) { + for op := range opDocExtras { + _, ok := opDocByName[op] + require.True(t, ok, "%s is in opDocExtra, but not opDocByName", op) + } + for op := range opcodeImmediateNotes { + _, ok := opDocByName[op] + require.True(t, ok, "%s is in opcodeImmediateNotes, but not opDocByName", op) + } +} + func TestOpGroupCoverage(t *testing.T) { partitiontest.PartitionTest(t) From 3e6aa96423e24f6af72cf62dd8aa94d61a429f00 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Mon, 13 Sep 2021 20:56:15 -0400 Subject: [PATCH 23/28] Generate specs --- data/transactions/logic/README.md | 2 +- data/transactions/logic/TEAL_opcodes.md | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/data/transactions/logic/README.md b/data/transactions/logic/README.md index 6d81af5db9..052a191f35 100644 --- a/data/transactions/logic/README.md +++ b/data/transactions/logic/README.md @@ -442,7 +442,7 @@ Currently, inner transactions may perform `pay`, `axfer`, `acfg`, and | --- | --- | | `itxn_begin` | Begin preparation of a new inner transaction | | `itxn_field f` | Set field F of the current inner transaction to X | -| `itxn_submit` | Execute the current inner transaction. Panic on any failure. | +| `itxn_submit` | Execute the current inner transaction. Fail if the transaction can not be applied. | | `itxn f` | push field F of the last inner transaction to stack | | `itxna f i` | push Ith valoue of the array field F of the last inner transaction to stack | diff --git a/data/transactions/logic/TEAL_opcodes.md b/data/transactions/logic/TEAL_opcodes.md index 79ca0b3032..c3e4e849dc 100644 --- a/data/transactions/logic/TEAL_opcodes.md +++ b/data/transactions/logic/TEAL_opcodes.md @@ -1300,6 +1300,8 @@ bitlen interprets arrays as big-endian integers, unlike setbit/getbit - LogicSigVersion >= 5 - Mode: Application +`itxn_begin` initializes Sender to the application address; Fee to the minimum allowable, taking into account MinTxnFee and credit from overpaying in earlier transactions; FirstValid/LastValid to the values in the top-level transaction, and all other fields to zero values. + ## itxn_field f - Opcode: 0xb2 {uint8 transaction field index} @@ -1309,12 +1311,14 @@ bitlen interprets arrays as big-endian integers, unlike setbit/getbit - LogicSigVersion >= 5 - Mode: Application +`itxn_field` fails if X is of the wrong type for F, including a byte array of the wrong size for use as an address when F is an address field. `tx_field` also fails if X is an account or asset that does not appear in `txn.Accounts` or `txn.ForeignAssets` of the top-level transaction. (Setting addresses in asset creation are exempted from this requirement.) + ## itxn_submit - Opcode: 0xb3 - Pops: _None_ - Pushes: _None_ -- Execute the current inner transaction. Panic on any failure. +- Execute the current inner transaction. Fail if the transaction can not be applied. - LogicSigVersion >= 5 - Mode: Application From 100227a657a78a21b0955bb437d46272cbebc539 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Mon, 13 Sep 2021 21:19:11 -0400 Subject: [PATCH 24/28] Spec details --- data/transactions/logic/README.md | 20 +++++++++++++++++--- data/transactions/logic/README_in.md | 18 ++++++++++++++++-- data/transactions/logic/TEAL_opcodes.md | 14 +++++++------- data/transactions/logic/doc.go | 14 +++++++------- 4 files changed, 47 insertions(+), 19 deletions(-) diff --git a/data/transactions/logic/README.md b/data/transactions/logic/README.md index 052a191f35..13cb46ebe8 100644 --- a/data/transactions/logic/README.md +++ b/data/transactions/logic/README.md @@ -5,7 +5,10 @@ also execute as _Applications_ which are invoked with explicit application call ## The Stack -The stack starts empty and contains values of either uint64 or bytes (`bytes` are implemented in Go as a []byte slice). Most operations act on the stack, popping arguments from it and pushing results to it. +The stack starts empty and contains values of either uint64 or bytes +(`bytes` are implemented in Go as a []byte slice and may not exceed +4096 bytes in length). Most operations act on the stack, popping +arguments from it and pushing results to it. The maximum stack depth is currently 1000. @@ -436,13 +439,24 @@ ID (prefixed by "appID"), or an account that has been rekeyed to that hash. Currently, inner transactions may perform `pay`, `axfer`, `acfg`, and -`afrz` effects. +`afrz` effects. After executing an inner transaction with +`itxn_submit`, the effects of the transaction are visible begining +with the next instruction with, for example, `balance` and +`min_balance` checks. + +Of the transaction Header fields, only a few fields may be set: +`Type`/`TypeEnum`, `Sender`, and `Fee`. For the specific fields of +each transaction types, any field, except `RekeyTo` may be set. This +allows, for example, clawback transactions, asset opt-ins, and asset +creates in addtion to the more common uses of `axfer` and `acfg`. All +fields default to the zero value, except those described under +`itxn_begin`. | Op | Description | | --- | --- | | `itxn_begin` | Begin preparation of a new inner transaction | | `itxn_field f` | Set field F of the current inner transaction to X | -| `itxn_submit` | Execute the current inner transaction. Fail if the transaction can not be applied. | +| `itxn_submit` | Execute the current inner transaction. Fail if 16 inner transactions have already been executed, or if the transaction itself fails. | | `itxn f` | push field F of the last inner transaction to stack | | `itxna f i` | push Ith valoue of the array field F of the last inner transaction to stack | diff --git a/data/transactions/logic/README_in.md b/data/transactions/logic/README_in.md index ad3b909047..46427e1619 100644 --- a/data/transactions/logic/README_in.md +++ b/data/transactions/logic/README_in.md @@ -5,7 +5,10 @@ also execute as _Applications_ which are invoked with explicit application call ## The Stack -The stack starts empty and contains values of either uint64 or bytes (`bytes` are implemented in Go as a []byte slice). Most operations act on the stack, popping arguments from it and pushing results to it. +The stack starts empty and contains values of either uint64 or bytes +(`bytes` are implemented in Go as a []byte slice and may not exceed +4096 bytes in length). Most operations act on the stack, popping +arguments from it and pushing results to it. The maximum stack depth is currently 1000. @@ -165,7 +168,18 @@ ID (prefixed by "appID"), or an account that has been rekeyed to that hash. Currently, inner transactions may perform `pay`, `axfer`, `acfg`, and -`afrz` effects. +`afrz` effects. After executing an inner transaction with +`itxn_submit`, the effects of the transaction are visible begining +with the next instruction with, for example, `balance` and +`min_balance` checks. + +Of the transaction Header fields, only a few fields may be set: +`Type`/`TypeEnum`, `Sender`, and `Fee`. For the specific fields of +each transaction types, any field, except `RekeyTo` may be set. This +allows, for example, clawback transactions, asset opt-ins, and asset +creates in addtion to the more common uses of `axfer` and `acfg`. All +fields default to the zero value, except those described under +`itxn_begin`. @@ Inner_Transactions.md @@ diff --git a/data/transactions/logic/TEAL_opcodes.md b/data/transactions/logic/TEAL_opcodes.md index c3e4e849dc..857040b06b 100644 --- a/data/transactions/logic/TEAL_opcodes.md +++ b/data/transactions/logic/TEAL_opcodes.md @@ -896,7 +896,7 @@ params: Txn.Accounts offset (or, since v4, an account address that appears in Tx - LogicSigVersion >= 2 - Mode: Application -params: Txn.Accounts offset (or, since v4, an account address that appears in Txn.Accounts or is Txn.Sender), application id (or, since v4, a Txn.ForeignApps offset), state key. Return: did_exist flag (top of the stack, 1 if exist and 0 otherwise), value. The value is zero (of type uint64) if the key does not exist. +params: Txn.Accounts offset (or, since v4, an account address that appears in Txn.Accounts or is Txn.Sender), application id (or, since v4, a Txn.ForeignApps offset), state key. Return: did_exist flag (top of the stack, 1 if the application existed and 0 otherwise), value. The value is zero (of type uint64) if the key does not exist. ## app_global_get @@ -918,7 +918,7 @@ params: state key. Return: value. The value is zero (of type uint64) if the key - LogicSigVersion >= 2 - Mode: Application -params: Txn.ForeignApps offset (or, since v4, an application id that appears in Txn.ForeignApps or is the CurrentApplicationID), state key. Return: did_exist flag (top of the stack, 1 if exist and 0 otherwise), value. The value is zero (of type uint64) if the key does not exist. +params: Txn.ForeignApps offset (or, since v4, an application id that appears in Txn.ForeignApps or is the CurrentApplicationID), state key. Return: did_exist flag (top of the stack, 1 if the application existed and 0 otherwise), value. The value is zero (of type uint64) if the key does not exist. ## app_local_put @@ -983,7 +983,7 @@ Deleting a key which is already absent has no effect on the application global s | 1 | AssetFrozen | uint64 | Is the asset frozen or not | -params: Txn.Accounts offset (or, since v4, an account address that appears in Txn.Accounts or is Txn.Sender), asset id (or, since v4, a Txn.ForeignAssets offset). Return: did_exist flag (1 if exist and 0 otherwise), value. +params: Txn.Accounts offset (or, since v4, an account address that appears in Txn.Accounts or is Txn.Sender), asset id (or, since v4, a Txn.ForeignAssets offset). Return: did_exist flag (1 if the asset existed and 0 otherwise), value. ## asset_params_get i @@ -1012,7 +1012,7 @@ params: Txn.Accounts offset (or, since v4, an account address that appears in Tx | 11 | AssetCreator | []byte | Creator address. LogicSigVersion >= 5. | -params: Before v4, Txn.ForeignAssets offset. Since v4, Txn.ForeignAssets offset or an asset id that appears in Txn.ForeignAssets. Return: did_exist flag (1 if exist and 0 otherwise), value. +params: Before v4, Txn.ForeignAssets offset. Since v4, Txn.ForeignAssets offset or an asset id that appears in Txn.ForeignAssets. Return: did_exist flag (1 if the asset existed and 0 otherwise), value. ## app_params_get i @@ -1038,7 +1038,7 @@ params: Before v4, Txn.ForeignAssets offset. Since v4, Txn.ForeignAssets offset | 8 | AppAddress | []byte | Address for which this application has authority | -params: Txn.ForeignApps offset or an app id that appears in Txn.ForeignApps. Return: did_exist flag (1 if exist and 0 otherwise), value. +params: Txn.ForeignApps offset or an app id that appears in Txn.ForeignApps. Return: did_exist flag (1 if the application existed and 0 otherwise), value. ## min_balance @@ -1311,14 +1311,14 @@ bitlen interprets arrays as big-endian integers, unlike setbit/getbit - LogicSigVersion >= 5 - Mode: Application -`itxn_field` fails if X is of the wrong type for F, including a byte array of the wrong size for use as an address when F is an address field. `tx_field` also fails if X is an account or asset that does not appear in `txn.Accounts` or `txn.ForeignAssets` of the top-level transaction. (Setting addresses in asset creation are exempted from this requirement.) +`itxn_field` fails if X is of the wrong type for F, including a byte array of the wrong size for use as an address when F is an address field. `itxn_field` also fails if X is an account or asset that does not appear in `txn.Accounts` or `txn.ForeignAssets` of the top-level transaction. (Setting addresses in asset creation are exempted from this requirement.) ## itxn_submit - Opcode: 0xb3 - Pops: _None_ - Pushes: _None_ -- Execute the current inner transaction. Fail if the transaction can not be applied. +- Execute the current inner transaction. Fail if 16 inner transactions have already been executed, or if the transaction itself fails. - LogicSigVersion >= 5 - Mode: Application diff --git a/data/transactions/logic/doc.go b/data/transactions/logic/doc.go index 746138614c..ab02718fd7 100644 --- a/data/transactions/logic/doc.go +++ b/data/transactions/logic/doc.go @@ -171,7 +171,7 @@ var opDocByName = map[string]string{ "log": "write bytes to log state of the current application", "itxn_begin": "Begin preparation of a new inner transaction", "itxn_field": "Set field F of the current inner transaction to X", - "itxn_submit": "Execute the current inner transaction. Fail if the transaction can not be applied.", + "itxn_submit": "Execute the current inner transaction. Fail if 16 inner transactions have already been executed, or if the transaction itself fails.", } // OpDoc returns a description of the op @@ -269,18 +269,18 @@ var opDocExtras = map[string]string{ "min_balance": "params: Before v4, Txn.Accounts offset. Since v4, Txn.Accounts offset or an account address that appears in Txn.Accounts or is Txn.Sender). Return: value.", "app_opted_in": "params: Txn.Accounts offset (or, since v4, an account address that appears in Txn.Accounts or is Txn.Sender), application id (or, since v4, a Txn.ForeignApps offset). Return: 1 if opted in and 0 otherwise.", "app_local_get": "params: Txn.Accounts offset (or, since v4, an account address that appears in Txn.Accounts or is Txn.Sender), state key. Return: value. The value is zero (of type uint64) if the key does not exist.", - "app_local_get_ex": "params: Txn.Accounts offset (or, since v4, an account address that appears in Txn.Accounts or is Txn.Sender), application id (or, since v4, a Txn.ForeignApps offset), state key. Return: did_exist flag (top of the stack, 1 if exist and 0 otherwise), value. The value is zero (of type uint64) if the key does not exist.", - "app_global_get_ex": "params: Txn.ForeignApps offset (or, since v4, an application id that appears in Txn.ForeignApps or is the CurrentApplicationID), state key. Return: did_exist flag (top of the stack, 1 if exist and 0 otherwise), value. The value is zero (of type uint64) if the key does not exist.", + "app_local_get_ex": "params: Txn.Accounts offset (or, since v4, an account address that appears in Txn.Accounts or is Txn.Sender), application id (or, since v4, a Txn.ForeignApps offset), state key. Return: did_exist flag (top of the stack, 1 if the application existed and 0 otherwise), value. The value is zero (of type uint64) if the key does not exist.", + "app_global_get_ex": "params: Txn.ForeignApps offset (or, since v4, an application id that appears in Txn.ForeignApps or is the CurrentApplicationID), state key. Return: did_exist flag (top of the stack, 1 if the application existed and 0 otherwise), value. The value is zero (of type uint64) if the key does not exist.", "app_global_get": "params: state key. Return: value. The value is zero (of type uint64) if the key does not exist.", "app_local_put": "params: Txn.Accounts offset (or, since v4, an account address that appears in Txn.Accounts or is Txn.Sender), state key, value.", "app_local_del": "params: Txn.Accounts offset (or, since v4, an account address that appears in Txn.Accounts or is Txn.Sender), state key.\n\nDeleting a key which is already absent has no effect on the application local state. (In particular, it does _not_ cause the program to fail.)", "app_global_del": "params: state key.\n\nDeleting a key which is already absent has no effect on the application global state. (In particular, it does _not_ cause the program to fail.)", - "asset_holding_get": "params: Txn.Accounts offset (or, since v4, an account address that appears in Txn.Accounts or is Txn.Sender), asset id (or, since v4, a Txn.ForeignAssets offset). Return: did_exist flag (1 if exist and 0 otherwise), value.", - "asset_params_get": "params: Before v4, Txn.ForeignAssets offset. Since v4, Txn.ForeignAssets offset or an asset id that appears in Txn.ForeignAssets. Return: did_exist flag (1 if exist and 0 otherwise), value.", - "app_params_get": "params: Txn.ForeignApps offset or an app id that appears in Txn.ForeignApps. Return: did_exist flag (1 if exist and 0 otherwise), value.", + "asset_holding_get": "params: Txn.Accounts offset (or, since v4, an account address that appears in Txn.Accounts or is Txn.Sender), asset id (or, since v4, a Txn.ForeignAssets offset). Return: did_exist flag (1 if the asset existed and 0 otherwise), value.", + "asset_params_get": "params: Before v4, Txn.ForeignAssets offset. Since v4, Txn.ForeignAssets offset or an asset id that appears in Txn.ForeignAssets. Return: did_exist flag (1 if the asset existed and 0 otherwise), value.", + "app_params_get": "params: Txn.ForeignApps offset or an app id that appears in Txn.ForeignApps. Return: did_exist flag (1 if the application existed and 0 otherwise), value.", "log": "`log` fails if called more than MaxLogCalls times in a program, or if the sum of logged bytes exceeds 1024 bytes.", "itxn_begin": "`itxn_begin` initializes Sender to the application address; Fee to the minimum allowable, taking into account MinTxnFee and credit from overpaying in earlier transactions; FirstValid/LastValid to the values in the top-level transaction, and all other fields to zero values.", - "itxn_field": "`itxn_field` fails if X is of the wrong type for F, including a byte array of the wrong size for use as an address when F is an address field. `tx_field` also fails if X is an account or asset that does not appear in `txn.Accounts` or `txn.ForeignAssets` of the top-level transaction. (Setting addresses in asset creation are exempted from this requirement.)", + "itxn_field": "`itxn_field` fails if X is of the wrong type for F, including a byte array of the wrong size for use as an address when F is an address field. `itxn_field` also fails if X is an account or asset that does not appear in `txn.Accounts` or `txn.ForeignAssets` of the top-level transaction. (Setting addresses in asset creation are exempted from this requirement.)", } // OpDocExtra returns extra documentation text about an op From 12de4ce591073e0ee1633b57d8b0de2199ee424f Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Mon, 13 Sep 2021 21:52:35 -0400 Subject: [PATCH 25/28] partiion fucking test --- data/transactions/logic/doc_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/data/transactions/logic/doc_test.go b/data/transactions/logic/doc_test.go index 96d51d13b7..f415016247 100644 --- a/data/transactions/logic/doc_test.go +++ b/data/transactions/logic/doc_test.go @@ -54,7 +54,11 @@ func TestOpDocs(t *testing.T) { require.Len(t, EcdsaCurveDocs, len(EcdsaCurveNames)) } +// TestDocStragglers confirms that we don't have any docs laying +// around for non-existent opcodes, most likely from a rename. func TestDocStragglers(t *testing.T) { + partitiontest.PartitionTest(t) + for op := range opDocExtras { _, ok := opDocByName[op] require.True(t, ok, "%s is in opDocExtra, but not opDocByName", op) From 24c76afc33b67926af79e5221975ae9fbd70a3eb Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Mon, 13 Sep 2021 22:27:45 -0400 Subject: [PATCH 26/28] spelling --- data/transactions/logic/README.md | 2 +- data/transactions/logic/README_in.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data/transactions/logic/README.md b/data/transactions/logic/README.md index 13cb46ebe8..26c5ae9eab 100644 --- a/data/transactions/logic/README.md +++ b/data/transactions/logic/README.md @@ -191,7 +191,7 @@ bytes on outputs. | `b%` | A modulo B, where A and B are byte-arrays interpreted as big-endian unsigned integers. Fail if B is zero. | These opcodes operate on the bits of byte-array values. The shorter -array is interpeted as though left padded with zeros until it is the +array is interpreted as though left padded with zeros until it is the same length as the other input. The returned values are the same length as the longest input. Therefore, unlike array arithmetic, these results may contain leading zero bytes. diff --git a/data/transactions/logic/README_in.md b/data/transactions/logic/README_in.md index 46427e1619..7521ff1cfc 100644 --- a/data/transactions/logic/README_in.md +++ b/data/transactions/logic/README_in.md @@ -105,7 +105,7 @@ bytes on outputs. @@ Byte_Array_Arithmetic.md @@ These opcodes operate on the bits of byte-array values. The shorter -array is interpeted as though left padded with zeros until it is the +array is interpreted as though left padded with zeros until it is the same length as the other input. The returned values are the same length as the longest input. Therefore, unlike array arithmetic, these results may contain leading zero bytes. From e1ad5625abe394ce4f1d8cd6c813e098110c6e3d Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Tue, 14 Sep 2021 12:22:27 -0400 Subject: [PATCH 27/28] Code review --- data/transactions/logic/README.md | 10 +++++----- data/transactions/logic/TEAL_opcodes.md | 10 +++++----- data/transactions/logic/assembler.go | 2 +- data/transactions/logic/assembler_test.go | 8 ++++---- data/transactions/logic/doc.go | 10 +++++----- data/transactions/logic/eval.go | 17 ++++++++++------- data/transactions/logic/evalAppTxn_test.go | 2 +- data/transactions/logic/evalStateful_test.go | 2 +- data/transactions/logic/fields.go | 12 ++++++------ data/transactions/logic/fields_string.go | 8 ++++---- ledger/apptxn_test.go | 12 ++++++++++-- 11 files changed, 52 insertions(+), 41 deletions(-) diff --git a/data/transactions/logic/README.md b/data/transactions/logic/README.md index 26c5ae9eab..95179f0783 100644 --- a/data/transactions/logic/README.md +++ b/data/transactions/logic/README.md @@ -313,10 +313,10 @@ Some of these have immediate data in the byte or bytes after the opcode. | 55 | LocalNumByteSlice | uint64 | Number of local state byteslices in ApplicationCall. LogicSigVersion >= 3. | | 56 | ExtraProgramPages | uint64 | Number of additional pages for each of the application's approval and clear state programs. An ExtraProgramPages of 1 means 2048 more total bytes, or 1024 for each program. LogicSigVersion >= 4. | | 57 | Nonparticipation | uint64 | Marks an account nonparticipating for rewards. LogicSigVersion >= 5. | -| 58 | Logs | []byte | Log messages emitted by an application call. LogicSigVersion >= 5. | -| 59 | NumLogs | uint64 | Number of Logs. LogicSigVersion >= 5. | -| 60 | EvalConfigAsset | uint64 | Asset ID allocated by the creation of an ASA. LogicSigVersion >= 5. | -| 61 | EvalApplicationID | uint64 | ApplicationID allocated by the creation of an application. LogicSigVersion >= 5. | +| 58 | Logs | []byte | Log messages emitted by an application call (itxn only). LogicSigVersion >= 5. | +| 59 | NumLogs | uint64 | Number of Logs (itxn only). LogicSigVersion >= 5. | +| 60 | CreatedAssetID | uint64 | Asset ID allocated by the creation of an ASA (itxn only). LogicSigVersion >= 5. | +| 61 | CreatedApplicationID | uint64 | ApplicationID allocated by the creation of an application (itxn only). LogicSigVersion >= 5. | Additional details in the [opcodes document](TEAL_opcodes.md#txn) on the `txn` op. @@ -458,7 +458,7 @@ fields default to the zero value, except those described under | `itxn_field f` | Set field F of the current inner transaction to X | | `itxn_submit` | Execute the current inner transaction. Fail if 16 inner transactions have already been executed, or if the transaction itself fails. | | `itxn f` | push field F of the last inner transaction to stack | -| `itxna f i` | push Ith valoue of the array field F of the last inner transaction to stack | +| `itxna f i` | push Ith value of the array field F of the last inner transaction to stack | # Assembler Syntax diff --git a/data/transactions/logic/TEAL_opcodes.md b/data/transactions/logic/TEAL_opcodes.md index 857040b06b..ec9b69faf8 100644 --- a/data/transactions/logic/TEAL_opcodes.md +++ b/data/transactions/logic/TEAL_opcodes.md @@ -480,10 +480,10 @@ Overflow is an error condition which halts execution and fails the transaction. | 55 | LocalNumByteSlice | uint64 | Number of local state byteslices in ApplicationCall. LogicSigVersion >= 3. | | 56 | ExtraProgramPages | uint64 | Number of additional pages for each of the application's approval and clear state programs. An ExtraProgramPages of 1 means 2048 more total bytes, or 1024 for each program. LogicSigVersion >= 4. | | 57 | Nonparticipation | uint64 | Marks an account nonparticipating for rewards. LogicSigVersion >= 5. | -| 58 | Logs | []byte | Log messages emitted by an application call. LogicSigVersion >= 5. | -| 59 | NumLogs | uint64 | Number of Logs. LogicSigVersion >= 5. | -| 60 | EvalConfigAsset | uint64 | Asset ID allocated by the creation of an ASA. LogicSigVersion >= 5. | -| 61 | EvalApplicationID | uint64 | ApplicationID allocated by the creation of an application. LogicSigVersion >= 5. | +| 58 | Logs | []byte | Log messages emitted by an application call (itxn only). LogicSigVersion >= 5. | +| 59 | NumLogs | uint64 | Number of Logs (itxn only). LogicSigVersion >= 5. | +| 60 | CreatedAssetID | uint64 | Asset ID allocated by the creation of an ASA (itxn only). LogicSigVersion >= 5. | +| 61 | CreatedApplicationID | uint64 | ApplicationID allocated by the creation of an application (itxn only). LogicSigVersion >= 5. | TypeEnum mapping: @@ -1336,7 +1336,7 @@ bitlen interprets arrays as big-endian integers, unlike setbit/getbit - Opcode: 0xb5 {uint8 transaction field index} {uint8 transaction field array index} - Pops: _None_ - Pushes: any -- push Ith valoue of the array field F of the last inner transaction to stack +- push Ith value of the array field F of the last inner transaction to stack - LogicSigVersion >= 5 - Mode: Application diff --git a/data/transactions/logic/assembler.go b/data/transactions/logic/assembler.go index 2302b27bb7..58e597ecd7 100644 --- a/data/transactions/logic/assembler.go +++ b/data/transactions/logic/assembler.go @@ -1055,7 +1055,7 @@ func assembleGtxnsas(ops *OpStream, spec *OpSpec, args []string) error { return nil } -// asmItxn delegates to asmItxnInternal or asmItxna depending on number of operands +// asmItxn delegates to asmItxnOnly or asmItxna depending on number of operands func asmItxn(ops *OpStream, spec *OpSpec, args []string) error { if len(args) == 1 { return asmItxnOnly(ops, spec, args) diff --git a/data/transactions/logic/assembler_test.go b/data/transactions/logic/assembler_test.go index 0cdd4e8df4..02a991d4a9 100644 --- a/data/transactions/logic/assembler_test.go +++ b/data/transactions/logic/assembler_test.go @@ -1346,10 +1346,10 @@ gtxn 12 Fee txn ExtraProgramPages txn Nonparticipation global CurrentApplicationAddress -txna Logs 1 -txn NumLogs -txn EvalConfigAsset -txn EvalApplicationID +itxna Logs 1 +itxn NumLogs +itxn CreatedAssetID +itxn CreatedApplicationID `, AssemblerMaxVersion) for _, globalField := range GlobalFieldNames { if !strings.Contains(text, globalField) { diff --git a/data/transactions/logic/doc.go b/data/transactions/logic/doc.go index ab02718fd7..29742a1fcd 100644 --- a/data/transactions/logic/doc.go +++ b/data/transactions/logic/doc.go @@ -96,7 +96,7 @@ var opDocByName = map[string]string{ "gtxnas": "push Xth value of the array field F from the Tth transaction in the current group", "gtxnsas": "pop an index A and an index B. push Bth value of the array field F from the Ath transaction in the current group", "itxn": "push field F of the last inner transaction to stack", - "itxna": "push Ith valoue of the array field F of the last inner transaction to stack", + "itxna": "push Ith value of the array field F of the last inner transaction to stack", "global": "push value from globals to stack", "load": "copy a value from scratch space to the stack. All scratch spaces are 0 at program start.", @@ -433,10 +433,10 @@ var txnFieldDocs = map[string]string{ "FreezeAssetAccount": "32 byte address of the account whose asset slot is being frozen or un-frozen", "FreezeAssetFrozen": "The new frozen value, 0 or 1", - "Logs": "Log messages emitted by an application call", - "NumLogs": "Number of Logs", - "EvalConfigAsset": "Asset ID allocated by the creation of an ASA", - "EvalApplicationID": "ApplicationID allocated by the creation of an application", + "Logs": "Log messages emitted by an application call (itxn only)", + "NumLogs": "Number of Logs (itxn only)", + "CreatedAssetID": "Asset ID allocated by the creation of an ASA (itxn only)", + "CreatedApplicationID": "ApplicationID allocated by the creation of an application (itxn only)", } // TxnFieldDocs are notes on fields available by `txn` and `gtxn` with extra versioning info if any diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index bf9544ca99..aa2744ddc4 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -1916,17 +1916,20 @@ func (cx *EvalContext) itxnFieldToStack(itxn *transactions.SignedTxnWithAD, fs t sv.Bytes = nilToEmpty([]byte(itxn.EvalDelta.Logs[arrayFieldIdx])) case NumLogs: sv.Uint = uint64(len(itxn.EvalDelta.Logs)) - case EvalConfigAsset: + case CreatedAssetID: sv.Uint = uint64(itxn.ApplyData.ConfigAsset) - case EvalApplicationID: + case CreatedApplicationID: sv.Uint = uint64(itxn.ApplyData.ApplicationID) + default: + err = fmt.Errorf("invalid txn field %d", fs.field) } + return + } + + if fs.field == GroupIndex || fs.field == TxID { + err = fmt.Errorf("illegal field for inner transaction %s", fs.field) } else { - if fs.field == GroupIndex { - err = errors.New("inner txn has no GroupIndex") - } else { - sv, err = cx.txnFieldToStack(&itxn.Txn, fs, arrayFieldIdx, 0) - } + sv, err = cx.txnFieldToStack(&itxn.Txn, fs, arrayFieldIdx, 0) } return } diff --git a/data/transactions/logic/evalAppTxn_test.go b/data/transactions/logic/evalAppTxn_test.go index 80bf0c01dd..693e1e82e4 100644 --- a/data/transactions/logic/evalAppTxn_test.go +++ b/data/transactions/logic/evalAppTxn_test.go @@ -400,7 +400,7 @@ func TestAssetFreeze(t *testing.T) { byte "https://gold.rush/" ; itxn_field ConfigAssetURL global CurrentApplicationAddress ; itxn_field ConfigAssetFreeze; itxn_submit - itxn EvalConfigAsset + itxn CreatedAssetID int 889 == ` diff --git a/data/transactions/logic/evalStateful_test.go b/data/transactions/logic/evalStateful_test.go index ee152b1832..08c0a40a8c 100644 --- a/data/transactions/logic/evalStateful_test.go +++ b/data/transactions/logic/evalStateful_test.go @@ -2389,7 +2389,7 @@ func TestReturnTypes(t *testing.T) { "gtxnas": "gtxnas 0 ApplicationArgs", "gtxnsas": "pop; pop; int 0; int 0; gtxnsas ApplicationArgs", "args": "args", - "itxn": "itxn_begin; int pay; itxn_field TypeEnum; itxn_submit; itxn EvalConfigAsset", + "itxn": "itxn_begin; int pay; itxn_field TypeEnum; itxn_submit; itxn CreatedConfigID", // This next one is a cop out. Can't use itxna Logs until we have inner appl "itxna": "itxn_begin; int pay; itxn_field TypeEnum; itxn_submit; itxn NumLogs", } diff --git a/data/transactions/logic/fields.go b/data/transactions/logic/fields.go index 6b0b59318d..32d29fd85c 100644 --- a/data/transactions/logic/fields.go +++ b/data/transactions/logic/fields.go @@ -155,11 +155,11 @@ const ( // NumLogs len(Logs) NumLogs - // EvalConfigAsset Transaction.ApplyData.EvalDelta.ConfigAsset - EvalConfigAsset + // CreatedAssetID Transaction.ApplyData.EvalDelta.ConfigAsset + CreatedAssetID - // EvalApplicationID Transaction.ApplyData.EvalDelta.ApplicationID - EvalApplicationID + // CreatedApplicationID Transaction.ApplyData.EvalDelta.ApplicationID + CreatedApplicationID invalidTxnField // fence for some setup that loops from Sender..invalidTxnField ) @@ -253,8 +253,8 @@ var txnFieldSpecs = []txnFieldSpec{ {Logs, StackBytes, 5, 5, true}, {NumLogs, StackUint64, 5, 5, true}, - {EvalConfigAsset, StackUint64, 5, 5, true}, - {EvalApplicationID, StackUint64, 5, 5, true}, + {CreatedAssetID, StackUint64, 5, 5, true}, + {CreatedApplicationID, StackUint64, 5, 5, true}, } // TxnaFieldNames are arguments to the 'txna' opcode diff --git a/data/transactions/logic/fields_string.go b/data/transactions/logic/fields_string.go index 9158065774..82abacb941 100644 --- a/data/transactions/logic/fields_string.go +++ b/data/transactions/logic/fields_string.go @@ -68,14 +68,14 @@ func _() { _ = x[Nonparticipation-57] _ = x[Logs-58] _ = x[NumLogs-59] - _ = x[EvalConfigAsset-60] - _ = x[EvalApplicationID-61] + _ = x[CreatedAssetID-60] + _ = x[CreatedApplicationID-61] _ = x[invalidTxnField-62] } -const _TxnField_name = "SenderFeeFirstValidFirstValidTimeLastValidNoteLeaseReceiverAmountCloseRemainderToVotePKSelectionPKVoteFirstVoteLastVoteKeyDilutionTypeTypeEnumXferAssetAssetAmountAssetSenderAssetReceiverAssetCloseToGroupIndexTxIDApplicationIDOnCompletionApplicationArgsNumAppArgsAccountsNumAccountsApprovalProgramClearStateProgramRekeyToConfigAssetConfigAssetTotalConfigAssetDecimalsConfigAssetDefaultFrozenConfigAssetUnitNameConfigAssetNameConfigAssetURLConfigAssetMetadataHashConfigAssetManagerConfigAssetReserveConfigAssetFreezeConfigAssetClawbackFreezeAssetFreezeAssetAccountFreezeAssetFrozenAssetsNumAssetsApplicationsNumApplicationsGlobalNumUintGlobalNumByteSliceLocalNumUintLocalNumByteSliceExtraProgramPagesNonparticipationLogsNumLogsEvalConfigAssetEvalApplicationIDinvalidTxnField" +const _TxnField_name = "SenderFeeFirstValidFirstValidTimeLastValidNoteLeaseReceiverAmountCloseRemainderToVotePKSelectionPKVoteFirstVoteLastVoteKeyDilutionTypeTypeEnumXferAssetAssetAmountAssetSenderAssetReceiverAssetCloseToGroupIndexTxIDApplicationIDOnCompletionApplicationArgsNumAppArgsAccountsNumAccountsApprovalProgramClearStateProgramRekeyToConfigAssetConfigAssetTotalConfigAssetDecimalsConfigAssetDefaultFrozenConfigAssetUnitNameConfigAssetNameConfigAssetURLConfigAssetMetadataHashConfigAssetManagerConfigAssetReserveConfigAssetFreezeConfigAssetClawbackFreezeAssetFreezeAssetAccountFreezeAssetFrozenAssetsNumAssetsApplicationsNumApplicationsGlobalNumUintGlobalNumByteSliceLocalNumUintLocalNumByteSliceExtraProgramPagesNonparticipationLogsNumLogsCreatedAssetIDCreatedApplicationIDinvalidTxnField" -var _TxnField_index = [...]uint16{0, 6, 9, 19, 33, 42, 46, 51, 59, 65, 81, 87, 98, 107, 115, 130, 134, 142, 151, 162, 173, 186, 198, 208, 212, 225, 237, 252, 262, 270, 281, 296, 313, 320, 331, 347, 366, 390, 409, 424, 438, 461, 479, 497, 514, 533, 544, 562, 579, 585, 594, 606, 621, 634, 652, 664, 681, 698, 714, 718, 725, 740, 757, 772} +var _TxnField_index = [...]uint16{0, 6, 9, 19, 33, 42, 46, 51, 59, 65, 81, 87, 98, 107, 115, 130, 134, 142, 151, 162, 173, 186, 198, 208, 212, 225, 237, 252, 262, 270, 281, 296, 313, 320, 331, 347, 366, 390, 409, 424, 438, 461, 479, 497, 514, 533, 544, 562, 579, 585, 594, 606, 621, 634, 652, 664, 681, 698, 714, 718, 725, 739, 759, 774} func (i TxnField) String() string { if i < 0 || i >= TxnField(len(_TxnField_index)-1) { diff --git a/ledger/apptxn_test.go b/ledger/apptxn_test.go index dd9df52a20..845bb54c48 100644 --- a/ledger/apptxn_test.go +++ b/ledger/apptxn_test.go @@ -889,9 +889,17 @@ func TestAsaDuringInit(t *testing.T) { byte "Gold" itxn_field ConfigAssetName itxn_submit - itxn EvalConfigAsset + itxn CreatedAssetID int 3 - assert + == + itxn CreatedApplicationID + int 0 + == + && + itxn NumLogs + int 0 + == + && `, } From 363445a5aebd8dae5bbd6beff6d7cf309e4498e3 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Tue, 14 Sep 2021 14:12:11 -0400 Subject: [PATCH 28/28] typo --- data/transactions/logic/evalStateful_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/transactions/logic/evalStateful_test.go b/data/transactions/logic/evalStateful_test.go index 08c0a40a8c..f270bd5414 100644 --- a/data/transactions/logic/evalStateful_test.go +++ b/data/transactions/logic/evalStateful_test.go @@ -2389,7 +2389,7 @@ func TestReturnTypes(t *testing.T) { "gtxnas": "gtxnas 0 ApplicationArgs", "gtxnsas": "pop; pop; int 0; int 0; gtxnsas ApplicationArgs", "args": "args", - "itxn": "itxn_begin; int pay; itxn_field TypeEnum; itxn_submit; itxn CreatedConfigID", + "itxn": "itxn_begin; int pay; itxn_field TypeEnum; itxn_submit; itxn CreatedAssetID", // This next one is a cop out. Can't use itxna Logs until we have inner appl "itxna": "itxn_begin; int pay; itxn_field TypeEnum; itxn_submit; itxn NumLogs", }