diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 210d7a3164..2bf6f2f272 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -5509,18 +5509,15 @@ func opItxnSubmit(cx *EvalContext) (err error) { parent = cx.currentTxID() } for itx := range cx.subtxns { - // The goal is to follow the same invariants used by the - // transaction pool. Namely that any transaction that makes it - // to Perform (which is equivalent to eval.applyTransaction) - // is authorized, and WellFormed. - txnErr := authorizedSender(cx, cx.subtxns[itx].Txn.Sender) - if txnErr != nil { - return txnErr - } + // The goal is to follow the same invariants used by the transaction + // pool. Namely that any transaction that makes it to Perform (which is + // equivalent to eval.applyTransaction) is WellFormed. Authorization + // must be checked later, to take state changes from earlier in the + // group into account. // Recall that WellFormed does not care about individual // transaction fees because of fee pooling. Checked above. - txnErr = cx.subtxns[itx].Txn.WellFormed(*cx.Specials, *cx.Proto) + txnErr := cx.subtxns[itx].Txn.WellFormed(*cx.Specials, *cx.Proto) if txnErr != nil { return txnErr } @@ -5639,7 +5636,11 @@ func opItxnSubmit(cx *EvalContext) (err error) { ep.Tracer.BeforeTxn(ep, i) } - err := cx.Ledger.Perform(i, ep) + err := authorizedSender(cx, ep.TxnGroup[i].Txn.Sender) + if err != nil { + return err + } + err = cx.Ledger.Perform(i, ep) if ep.Tracer != nil { ep.Tracer.AfterTxn(ep, i, ep.TxnGroup[i].ApplyData, err) diff --git a/data/transactions/logic/evalAppTxn_test.go b/data/transactions/logic/evalAppTxn_test.go index 9a6d0ebc41..562142fdec 100644 --- a/data/transactions/logic/evalAppTxn_test.go +++ b/data/transactions/logic/evalAppTxn_test.go @@ -375,6 +375,34 @@ func TestRekeyBack(t *testing.T) { }) } +// TestRekeyInnerGroup ensures that in an inner group, if an account is +// rekeyed, it can not be used (by the previously owning app) later in the +// group. +func TestRekeyInnerGroup(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + rekeyAndUse := ` + itxn_begin + // pay 0 to the zero address, and rekey a junk addr + int pay; itxn_field TypeEnum + global ZeroAddress; byte 0x01; b|; itxn_field RekeyTo + itxn_next + // try to perform the same 0 pay, but fail because tx0 gave away control + int pay; itxn_field TypeEnum + itxn_submit + int 1 +` + + // v6 added inner rekey + TestLogicRange(t, 6, 0, func(t *testing.T, ep *EvalParams, tx *transactions.Transaction, ledger *Ledger) { + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + // fund the app account + ledger.NewAccount(basics.AppIndex(888).Address(), 1_000_000) + TestApp(t, rekeyAndUse, ep, "unauthorized AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVIOOBQA") + }) +} + func TestDefaultSender(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel()