diff options
Diffstat (limited to 'data/transactions/logic/evalAppTxn_test.go')
-rw-r--r-- | data/transactions/logic/evalAppTxn_test.go | 1413 |
1 files changed, 1218 insertions, 195 deletions
diff --git a/data/transactions/logic/evalAppTxn_test.go b/data/transactions/logic/evalAppTxn_test.go index 4c9f3a4f0..786b60bf8 100644 --- a/data/transactions/logic/evalAppTxn_test.go +++ b/data/transactions/logic/evalAppTxn_test.go @@ -14,92 +14,104 @@ // You should have received a copy of the GNU Affero General Public License // along with go-algorand. If not, see <https://www.gnu.org/licenses/>. -package logic +package logic_test import ( + "encoding/hex" "fmt" + "strings" "testing" "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/data/transactions" + . "github.com/algorand/go-algorand/data/transactions/logic" + "github.com/algorand/go-algorand/data/txntest" + "github.com/algorand/go-algorand/protocol" "github.com/stretchr/testify/require" ) func TestInnerTypesV5(t *testing.T) { - v5, _ := makeSampleEnvWithVersion(5) + v5, _, _ := MakeSampleEnvWithVersion(5) // not alllowed in v5 - testApp(t, "itxn_begin; byte \"keyreg\"; itxn_field Type; itxn_submit; int 1;", v5, "keyreg is not a valid Type for itxn_field") - testApp(t, "itxn_begin; int keyreg; itxn_field TypeEnum; itxn_submit; int 1;", v5, "keyreg is not a valid Type for itxn_field") + TestApp(t, "itxn_begin; byte \"keyreg\"; itxn_field Type; itxn_submit; int 1;", v5, "keyreg is not a valid Type for itxn_field") + TestApp(t, "itxn_begin; int keyreg; itxn_field TypeEnum; itxn_submit; int 1;", v5, "keyreg is not a valid Type for itxn_field") + + TestApp(t, "itxn_begin; byte \"appl\"; itxn_field Type; itxn_submit; int 1;", v5, "appl is not a valid Type for itxn_field") + TestApp(t, "itxn_begin; int appl; itxn_field TypeEnum; itxn_submit; int 1;", v5, "appl is not a valid Type for itxn_field") } func TestCurrentInnerTypes(t *testing.T) { - ep, ledger := makeSampleEnv() - 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, "unknown tx type") + ep, tx, ledger := MakeSampleEnv() + 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, "unknown tx type") // bad type - testApp(t, "itxn_begin; byte \"pya\"; itxn_field Type; itxn_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("itxn_begin; int pay; itxn_field Type; itxn_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("itxn_begin; byte \"pay\"; itxn_field TypeEnum; itxn_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 allowed yet - 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, "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") + // some bad types + 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, "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, "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, "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") + 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") - // alllowed since v6 - testApp(t, "itxn_begin; byte \"keyreg\"; itxn_field Type; itxn_submit; int 1;", ep, "insufficient balance") - testApp(t, "itxn_begin; int keyreg; itxn_field TypeEnum; itxn_submit; int 1;", ep, "insufficient balance") + // allowed since v6 + TestApp(t, "itxn_begin; byte \"keyreg\"; itxn_field Type; itxn_submit; int 1;", ep, "insufficient balance") + TestApp(t, "itxn_begin; int keyreg; itxn_field TypeEnum; itxn_submit; int 1;", ep, "insufficient balance") + TestApp(t, "itxn_begin; byte \"appl\"; itxn_field Type; itxn_submit; int 1;", ep, "insufficient balance") + TestApp(t, "itxn_begin; int appl; 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.NewApp(tx.Receiver, 888, basics.AppParams{}) ledger.NewAccount(basics.AppIndex(888).Address(), 200000) - 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) + 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) + 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, "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") + ep, _, _ := MakeSampleEnv() + 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, "itxn_begin; byte \"01234567890123456789012345678901\"; itxn_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, "itxn_begin; byte \"GAYTEMZUGU3DOOBZGAYTEMZUGU3DOOBZGAYTEMZUGU3DOOBZGAYZIZD42E\"; itxn_field AssetCloseTo;", + TestApp(t, "itxn_begin; byte \"GAYTEMZUGU3DOOBZGAYTEMZUGU3DOOBZGAYTEMZUGU3DOOBZGAYZIZD42E\"; itxn_field AssetCloseTo;", ep, "not an address") - 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") + 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 appAddr(id int) basics.Address { + return basics.AppIndex(id).Address() } func TestAppPay(t *testing.T) { @@ -114,25 +126,25 @@ func TestAppPay(t *testing.T) { int 1 ` - ep, ledger := makeSampleEnv() - ledger.NewApp(ep.Txn.Txn.Receiver, 888, basics.AppParams{}) - testApp(t, "txn Sender; balance; int 0; ==;", ep) - testApp(t, "txn Sender; txn Accounts 1; int 100"+pay, ep, "unauthorized") - testApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100"+pay, ep, + ep, tx, ledger := MakeSampleEnv() + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + TestApp(t, "txn Sender; balance; int 0; ==;", ep) + TestApp(t, "txn Sender; txn Accounts 1; int 100"+pay, ep, "unauthorized") + TestApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100"+pay, ep, "insufficient balance") - ledger.NewAccount(ledger.ApplicationID().Address(), 1000000) + ledger.NewAccount(appAddr(888), 1000000) - // You might expect this to fail because of min balance issue + // You might NewExpect this to fail because of min balance issue // (receiving account only gets 100 microalgos). It does not fail at // this level, instead, we must be certain that the existing min // balance check in eval.transaction() properly notices and fails // the transaction later. This fits with the model that we check // min balances once at the end of each "top-level" transaction. - testApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100"+pay, ep) + TestApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100"+pay, ep) // 100 of 1000000 spent, plus MinTxnFee in our fake protocol is 1001 - testApp(t, "global CurrentApplicationAddress; balance; int 998899; ==", ep) - testApp(t, "txn Receiver; balance; int 100; ==", ep) + TestApp(t, "global CurrentApplicationAddress; balance; int 998899; ==", ep) + TestApp(t, "txn Receiver; balance; int 100; ==", ep) close := ` itxn_begin @@ -141,16 +153,16 @@ func TestAppPay(t *testing.T) { itxn_submit int 1 ` - testApp(t, close, ep) - testApp(t, "global CurrentApplicationAddress; balance; !", ep) + TestApp(t, close, ep) + TestApp(t, "global CurrentApplicationAddress; balance; !", ep) // Receiver got most of the algos (except 1001 for fee) - testApp(t, "txn Receiver; balance; int 997998; ==", ep) + TestApp(t, "txn Receiver; balance; int 997998; ==", ep) } func TestAppAssetOptIn(t *testing.T) { - ep, ledger := makeSampleEnv() + ep, tx, ledger := MakeSampleEnv() // Establish 888 as the app id, and fund it. - ledger.NewApp(ep.Txn.Txn.Receiver, 888, basics.AppParams{}) + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) ledger.NewAccount(basics.AppIndex(888).Address(), 200000) axfer := ` @@ -162,9 +174,9 @@ 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 + TestApp(t, axfer, ep, "invalid Asset reference") + tx.ForeignAssets = append(tx.ForeignAssets, 25) + TestApp(t, axfer, ep, "not opted in") // app account not opted in optin := ` itxn_begin int axfer; itxn_field TypeEnum; @@ -174,23 +186,23 @@ global CurrentApplicationAddress; itxn_field AssetReceiver; itxn_submit int 1 ` - testApp(t, optin, ep, "does not exist") + TestApp(t, optin, ep, "does not exist") // Asset 25 - ledger.NewAsset(ep.Txn.Txn.Sender, 25, basics.AssetParams{ + ledger.NewAsset(tx.Sender, 25, basics.AssetParams{ Total: 10, UnitName: "x", AssetName: "Cross", }) - testApp(t, optin, ep) + TestApp(t, optin, ep) - testApp(t, axfer, ep, "insufficient balance") // opted in, but balance=0 + TestApp(t, axfer, ep, "insufficient balance") // opted in, but balance=0 // Fund the app account with the asset ledger.NewHolding(basics.AppIndex(888).Address(), 25, 5, false) - testApp(t, axfer, ep) - testApp(t, axfer, ep) - testApp(t, axfer, ep, "insufficient balance") // balance = 1, tried to move 2) - testApp(t, "global CurrentApplicationAddress; int 25; asset_holding_get AssetBalance; assert; int 1; ==", ep) + TestApp(t, axfer, ep) + TestApp(t, axfer, ep) + TestApp(t, axfer, ep, "insufficient balance") // balance = 1, tried to move 2) + TestApp(t, "global CurrentApplicationAddress; int 25; asset_holding_get AssetBalance; assert; int 1; ==", ep) close := ` itxn_begin @@ -202,8 +214,8 @@ txn Sender; itxn_field AssetCloseTo; itxn_submit int 1 ` - testApp(t, close, ep) - testApp(t, "global CurrentApplicationAddress; int 25; asset_holding_get AssetBalance; !; assert; !", ep) + TestApp(t, close, ep) + TestApp(t, "global CurrentApplicationAddress; int 25; asset_holding_get AssetBalance; !; assert; !", ep) } func TestRekeyPay(t *testing.T) { @@ -217,13 +229,13 @@ func TestRekeyPay(t *testing.T) { itxn_submit ` - ep, ledger := makeSampleEnv() - ledger.NewApp(ep.Txn.Txn.Receiver, 888, basics.AppParams{}) - testApp(t, "txn Sender; balance; int 0; ==;", ep) - testApp(t, "txn Sender; txn Accounts 1; int 100"+pay, ep, "unauthorized") - ledger.NewAccount(ep.Txn.Txn.Sender, 120+ep.Proto.MinTxnFee) - ledger.Rekey(ep.Txn.Txn.Sender, basics.AppIndex(888).Address()) - testApp(t, "txn Sender; txn Accounts 1; int 100"+pay+"; int 1", ep) + ep, tx, ledger := MakeSampleEnv() + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + TestApp(t, "txn Sender; balance; int 0; ==;", ep) + TestApp(t, "txn Sender; txn Accounts 1; int 100"+pay, ep, "unauthorized") + ledger.NewAccount(tx.Sender, 120+ep.Proto.MinTxnFee) + ledger.Rekey(tx.Sender, basics.AppIndex(888).Address()) + TestApp(t, "txn Sender; txn Accounts 1; int 100"+pay+"; int 1", ep) // Note that the Sender would fail min balance check if we did it here. // It seems proper to wait until end of txn though. // See explanation in logicLedger's Perform() @@ -242,15 +254,15 @@ func TestRekeyBack(t *testing.T) { itxn_submit ` - ep, ledger := makeSampleEnv() - ledger.NewApp(ep.Txn.Txn.Receiver, 888, basics.AppParams{}) - testApp(t, "txn Sender; balance; int 0; ==;", ep) - testApp(t, "txn Sender; txn Accounts 1; int 100"+payAndUnkey, ep, "unauthorized") - ledger.NewAccount(ep.Txn.Txn.Sender, 120+3*ep.Proto.MinTxnFee) - ledger.Rekey(ep.Txn.Txn.Sender, basics.AppIndex(888).Address()) - testApp(t, "txn Sender; txn Accounts 1; int 100"+payAndUnkey+"; int 1", ep) + ep, tx, ledger := MakeSampleEnv() + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + TestApp(t, "txn Sender; balance; int 0; ==;", ep) + TestApp(t, "txn Sender; txn Accounts 1; int 100"+payAndUnkey, ep, "unauthorized") + ledger.NewAccount(tx.Sender, 120+3*ep.Proto.MinTxnFee) + ledger.Rekey(tx.Sender, basics.AppIndex(888).Address()) + TestApp(t, "txn Sender; txn Accounts 1; int 100"+payAndUnkey+"; int 1", ep) // now rekeyed back to original - testApp(t, "txn Sender; txn Accounts 1; int 100"+payAndUnkey, ep, "unauthorized") + TestApp(t, "txn Sender; txn Accounts 1; int 100"+payAndUnkey, ep, "unauthorized") } func TestDefaultSender(t *testing.T) { @@ -263,13 +275,13 @@ func TestDefaultSender(t *testing.T) { itxn_submit ` - ep, ledger := makeSampleEnv() - ledger.NewApp(ep.Txn.Txn.Receiver, 888, basics.AppParams{}) - ep.Txn.Txn.Accounts = append(ep.Txn.Txn.Accounts, ledger.ApplicationID().Address()) - testApp(t, "txn Accounts 1; int 100"+pay, ep, "insufficient balance") - ledger.NewAccount(ledger.ApplicationID().Address(), 1000000) - testApp(t, "txn Accounts 1; int 100"+pay+"int 1", ep) - testApp(t, "global CurrentApplicationAddress; balance; int 998899; ==", ep) + ep, tx, ledger := MakeSampleEnv() + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + tx.Accounts = append(tx.Accounts, appAddr(888)) + TestApp(t, "txn Accounts 1; int 100"+pay, ep, "insufficient balance") + ledger.NewAccount(appAddr(888), 1000000) + TestApp(t, "txn Accounts 1; int 100"+pay+"int 1", ep) + TestApp(t, "global CurrentApplicationAddress; balance; int 998899; ==", ep) } func TestAppAxfer(t *testing.T) { @@ -285,34 +297,34 @@ func TestAppAxfer(t *testing.T) { itxn_submit ` - ep, ledger := makeSampleEnv() - ledger.NewApp(ep.Txn.Txn.Receiver, 888, basics.AppParams{}) - ledger.NewAsset(ep.Txn.Txn.Receiver, 777, basics.AssetParams{}) // not in foreign-assets of sample - ledger.NewAsset(ep.Txn.Txn.Receiver, 77, basics.AssetParams{}) // in foreign-assets of sample - testApp(t, "txn Sender; int 777; asset_holding_get AssetBalance; assert; int 0; ==;", ep, + ep, tx, ledger := MakeSampleEnv() + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + ledger.NewAsset(tx.Receiver, 777, basics.AssetParams{}) // not in foreign-assets of sample + ledger.NewAsset(tx.Receiver, 77, basics.AssetParams{}) // in foreign-assets of sample + TestApp(t, "txn Sender; int 777; asset_holding_get AssetBalance; assert; int 0; ==;", ep, "invalid Asset reference") // 777 not in foreign-assets - testApp(t, "txn Sender; int 77; asset_holding_get AssetBalance; assert; int 0; ==;", ep, + TestApp(t, "txn Sender; int 77; asset_holding_get AssetBalance; assert; int 0; ==;", ep, "assert failed") // because Sender not opted-in - testApp(t, "global CurrentApplicationAddress; int 77; asset_holding_get AssetBalance; assert; int 0; ==;", ep, + TestApp(t, "global CurrentApplicationAddress; int 77; asset_holding_get AssetBalance; assert; int 0; ==;", ep, "assert failed") // app account not opted in - ledger.NewAccount(ledger.ApplicationID().Address(), 10000) // plenty for fees - ledger.NewHolding(ledger.ApplicationID().Address(), 77, 3000, false) - testApp(t, "global CurrentApplicationAddress; int 77; asset_holding_get AssetBalance; assert; int 3000; ==;", ep) + ledger.NewAccount(appAddr(888), 10000) // plenty for fees + ledger.NewHolding(appAddr(888), 77, 3000, false) + TestApp(t, "global CurrentApplicationAddress; int 77; asset_holding_get AssetBalance; assert; int 3000; ==;", ep) - testApp(t, "txn Sender; txn Accounts 1; int 100"+axfer, ep, "unauthorized") - testApp(t, "global CurrentApplicationAddress; txn Accounts 0; int 100"+axfer, ep, - fmt.Sprintf("Receiver (%s) not opted in", ep.Txn.Txn.Sender)) // txn.Sender (receiver of the axfer) isn't opted in - testApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100000"+axfer, ep, + TestApp(t, "txn Sender; txn Accounts 1; int 100"+axfer, ep, "unauthorized") + TestApp(t, "global CurrentApplicationAddress; txn Accounts 0; int 100"+axfer, ep, + fmt.Sprintf("Receiver (%s) not opted in", tx.Sender)) // txn.Sender (receiver of the axfer) isn't opted in + TestApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100000"+axfer, ep, "insufficient balance") // Temporarily remove from ForeignAssets to ensure App Account // doesn't get some sort of free pass to send arbitrary assets. - save := ep.Txn.Txn.ForeignAssets - ep.Txn.Txn.ForeignAssets = []basics.AssetIndex{6, 10} - testApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100000"+axfer, ep, + save := tx.ForeignAssets + tx.ForeignAssets = []basics.AssetIndex{6, 10} + TestApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100000"+axfer, ep, "invalid Asset reference 77") - ep.Txn.Txn.ForeignAssets = save + tx.ForeignAssets = save noid := ` itxn_begin @@ -323,14 +335,14 @@ func TestAppAxfer(t *testing.T) { 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())) + TestApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100"+noid+"int 1", ep, + fmt.Sprintf("Sender (%s) not opted in to 0", appAddr(888))) - testApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100"+axfer+"int 1", ep) + TestApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100"+axfer+"int 1", ep) // 100 of 3000 spent - testApp(t, "global CurrentApplicationAddress; int 77; asset_holding_get AssetBalance; assert; int 2900; ==", ep) - testApp(t, "txn Accounts 1; int 77; asset_holding_get AssetBalance; assert; int 100; ==", ep) + TestApp(t, "global CurrentApplicationAddress; int 77; asset_holding_get AssetBalance; assert; int 2900; ==", ep) + TestApp(t, "txn Accounts 1; int 77; asset_holding_get AssetBalance; assert; int 100; ==", ep) } func TestExtraFields(t *testing.T) { @@ -345,11 +357,11 @@ func TestExtraFields(t *testing.T) { itxn_submit ` - ep, ledger := makeSampleEnv() - ledger.NewApp(ep.Txn.Txn.Receiver, 888, basics.AppParams{}) - testApp(t, "txn Sender; balance; int 0; ==;", ep) - testApp(t, "txn Sender; txn Accounts 1; int 100"+pay, ep, "unauthorized") - testApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100"+pay, ep, + ep, tx, ledger := MakeSampleEnv() + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + TestApp(t, "txn Sender; balance; int 0; ==;", ep) + TestApp(t, "txn Sender; txn Accounts 1; int 100"+pay, ep, "unauthorized") + TestApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100"+pay, ep, "non-zero fields for type axfer") } @@ -363,14 +375,16 @@ func TestBadFieldV5(t *testing.T) { int pay itxn_field TypeEnum txn Receiver - itxn_field RekeyTo // NOT ALLOWED + itxn_field Sender // Will be changed to RekeyTo itxn_submit ` - ep, ledger := makeSampleEnvWithVersion(5) - ledger.NewApp(ep.Txn.Txn.Receiver, 888, basics.AppParams{}) - testApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100"+pay, ep, - "invalid itxn_field RekeyTo") + ep, tx, ledger := MakeSampleEnvWithVersion(5) + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + // Assemble a good program, then change the field to a bad one + ops := TestProg(t, "global CurrentApplicationAddress; txn Accounts 1; int 100"+pay, 5) + ops.Program[len(ops.Program)-2] = byte(RekeyTo) + TestAppBytes(t, ops.Program, ep, "invalid itxn_field RekeyTo") } func TestBadField(t *testing.T) { @@ -383,19 +397,20 @@ func TestBadField(t *testing.T) { int pay itxn_field TypeEnum txn Receiver - itxn_field RekeyTo // ALLOWED, since v6 + itxn_field RekeyTo // ALLOWED, since v6 int 10 - itxn_field FirstValid // NOT ALLOWED + itxn_field Amount // Will be changed to FirstValid 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 itxn_field FirstValid") + ep, tx, ledger := MakeSampleEnv() + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + ops := TestProg(t, "global CurrentApplicationAddress; txn Accounts 1; int 100"+pay, AssemblerMaxVersion) + ops.Program[len(ops.Program)-2] = byte(FirstValid) + TestAppBytes(t, ops.Program, ep, "invalid itxn_field FirstValid") } -func TestNumInner(t *testing.T) { +func TestNumInnerShallow(t *testing.T) { pay := ` itxn_begin int 1 @@ -407,15 +422,68 @@ func TestNumInner(t *testing.T) { itxn_submit ` - ep, ledger := makeSampleEnv() - ledger.NewApp(ep.Txn.Txn.Receiver, 888, basics.AppParams{}) - ledger.NewAccount(ledger.ApplicationID().Address(), 1000000) - testApp(t, pay+";int 1", ep) - testApp(t, pay+pay+";int 1", ep) - testApp(t, pay+pay+pay+";int 1", ep) - testApp(t, pay+pay+pay+pay+";int 1", ep) + ep, tx, ledger := MakeSampleEnv() + ep.Proto.EnableInnerTransactionPooling = false + ep.Reset() + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + ledger.NewAccount(appAddr(888), 1000000) + TestApp(t, pay+";int 1", ep) + TestApp(t, pay+pay+";int 1", ep) + 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, "too many inner transactions") + TestApp(t, pay+pay+pay+pay+pay+";int 1", ep, "too many inner transactions") + + ep, tx, ledger = MakeSampleEnv() + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + ledger.NewAccount(appAddr(888), 1000000) + TestApp(t, pay+";int 1", ep) + TestApp(t, pay+pay+";int 1", ep) + TestApp(t, pay+pay+pay+";int 1", ep) + TestApp(t, pay+pay+pay+pay+";int 1", ep) + // In the sample proto, MaxInnerTransactions = 4, but when pooling you get + // MaxTxGroupSize (here, 8) * that. + TestApp(t, pay+pay+pay+pay+pay+";int 1", ep) + TestApp(t, strings.Repeat(pay, 32)+";int 1", ep) + TestApp(t, strings.Repeat(pay, 33)+";int 1", ep, "too many inner transactions") +} + +// TestNumInnerPooled ensures that inner call limits are pooled across app calls +// in a group. +func TestNumInnerPooled(t *testing.T) { + pay := ` + itxn_begin + int 1 + itxn_field Amount + txn Accounts 0 + itxn_field Receiver + int pay + itxn_field TypeEnum + itxn_submit +` + + tx := txntest.Txn{ + Type: protocol.ApplicationCallTx, + }.SignedTxn() + ledger := MakeLedger(nil) + ledger.NewApp(tx.Txn.Receiver, 888, basics.AppParams{}) + ledger.NewAccount(appAddr(888), 1000000) + short := pay + ";int 1" + long := strings.Repeat(pay, 17) + ";int 1" // More than half allowed + + grp := MakeSampleTxnGroup(tx) + TestApps(t, []string{short, ""}, grp, LogicVersion, ledger) + TestApps(t, []string{short, short}, grp, LogicVersion, ledger) + TestApps(t, []string{long, ""}, grp, LogicVersion, ledger) + TestApps(t, []string{short, long}, grp, LogicVersion, ledger) + TestApps(t, []string{long, short}, grp, LogicVersion, ledger) + TestApps(t, []string{long, long}, grp, LogicVersion, ledger, + NewExpect(1, "too many inner transactions")) + grp = append(grp, grp[0]) + TestApps(t, []string{short, long, long}, grp, LogicVersion, ledger, + NewExpect(2, "too many inner transactions")) + TestApps(t, []string{long, long, long}, grp, LogicVersion, ledger, + NewExpect(1, "too many inner transactions")) } func TestAssetCreate(t *testing.T) { @@ -436,12 +504,12 @@ func TestAssetCreate(t *testing.T) { itxn_submit int 1 ` - ep, ledger := makeSampleEnv() - ledger.NewApp(ep.Txn.Txn.Receiver, 888, basics.AppParams{}) - testApp(t, create, ep, "insufficient balance") + ep, tx, ledger := MakeSampleEnv() + ledger.NewApp(tx.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) + ledger.NewAccount(appAddr(888), MakeTestProto().MinTxnFee) + TestApp(t, create, ep) } func TestAssetFreeze(t *testing.T) { @@ -456,98 +524,98 @@ func TestAssetFreeze(t *testing.T) { global CurrentApplicationAddress ; itxn_field ConfigAssetFreeze; itxn_submit itxn CreatedAssetID - int 889 + int 5000 == ` - ep, ledger := makeSampleEnv() - ledger.NewApp(ep.Txn.Txn.Receiver, 888, basics.AppParams{}) + ep, tx, ledger := MakeSampleEnv() + ledger.NewApp(tx.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) + ledger.NewAccount(appAddr(888), 12*MakeTestProto().MinTxnFee) + TestApp(t, create, ep) freeze := ` itxn_begin int afrz ; itxn_field TypeEnum - int 889 ; itxn_field FreezeAsset + int 5000 ; 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") - 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) + TestApp(t, freeze, ep, "invalid Asset reference") + tx.ForeignAssets = []basics.AssetIndex{basics.AssetIndex(5000)} + tx.ApplicationArgs = [][]byte{{0x01}} + TestApp(t, freeze, ep, "does not hold Asset") + ledger.NewHolding(tx.Receiver, 5000, 55, false) + TestApp(t, freeze, ep) + holding, err := ledger.AssetHolding(tx.Receiver, 5000) 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) + tx.ApplicationArgs = [][]byte{{0x00}} + TestApp(t, freeze, ep) + holding, err = ledger.AssetHolding(tx.Receiver, 5000) require.NoError(t, err) require.Equal(t, false, holding.Frozen) } func TestFieldSetting(t *testing.T) { - ep, ledger := makeSampleEnv() - ledger.NewApp(ep.Txn.Txn.Receiver, 888, basics.AppParams{}) - ledger.NewAccount(ledger.ApplicationID().Address(), 10*defaultEvalProto().MinTxnFee) - testApp(t, "itxn_begin; int 500; bzero; itxn_field Note; int 1", ep) - testApp(t, "itxn_begin; int 501; bzero; itxn_field Note; int 1", ep, + ep, tx, ledger := MakeSampleEnv() + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + ledger.NewAccount(appAddr(888), 10*MakeTestProto().MinTxnFee) + TestApp(t, "itxn_begin; int 500; bzero; itxn_field Note; int 1", ep) + TestApp(t, "itxn_begin; int 501; bzero; itxn_field Note; int 1", ep, "Note may not exceed") - testApp(t, "itxn_begin; int 32; bzero; itxn_field VotePK; int 1", ep) - testApp(t, "itxn_begin; int 31; bzero; itxn_field VotePK; int 1", ep, + TestApp(t, "itxn_begin; int 32; bzero; itxn_field VotePK; int 1", ep) + TestApp(t, "itxn_begin; int 31; bzero; itxn_field VotePK; int 1", ep, "VotePK must be 32") - testApp(t, "itxn_begin; int 32; bzero; itxn_field SelectionPK; int 1", ep) - testApp(t, "itxn_begin; int 33; bzero; itxn_field SelectionPK; int 1", ep, + TestApp(t, "itxn_begin; int 32; bzero; itxn_field SelectionPK; int 1", ep) + TestApp(t, "itxn_begin; int 33; bzero; itxn_field SelectionPK; int 1", ep, "SelectionPK must be 32") - testApp(t, "itxn_begin; int 32; bzero; itxn_field RekeyTo; int 1", ep) - testApp(t, "itxn_begin; int 31; bzero; itxn_field RekeyTo; int 1", ep, + TestApp(t, "itxn_begin; int 32; bzero; itxn_field RekeyTo; int 1", ep) + TestApp(t, "itxn_begin; int 31; bzero; itxn_field RekeyTo; int 1", ep, "not an address") - testApp(t, "itxn_begin; int 6; bzero; itxn_field ConfigAssetUnitName; int 1", ep) - testApp(t, "itxn_begin; int 7; bzero; itxn_field ConfigAssetUnitName; int 1", ep, + TestApp(t, "itxn_begin; int 6; bzero; itxn_field ConfigAssetUnitName; int 1", ep) + TestApp(t, "itxn_begin; int 7; bzero; itxn_field ConfigAssetUnitName; int 1", ep, "value is too long") - testApp(t, "itxn_begin; int 12; bzero; itxn_field ConfigAssetName; int 1", ep) - testApp(t, "itxn_begin; int 13; bzero; itxn_field ConfigAssetName; int 1", ep, + TestApp(t, "itxn_begin; int 12; bzero; itxn_field ConfigAssetName; int 1", ep) + TestApp(t, "itxn_begin; int 13; bzero; itxn_field ConfigAssetName; int 1", ep, "value is too long") } func TestInnerGroup(t *testing.T) { - ep, ledger := makeSampleEnv() - ledger.NewApp(ep.Txn.Txn.Receiver, 888, basics.AppParams{}) + ep, tx, ledger := MakeSampleEnv() + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) // Need both fees and both payments - ledger.NewAccount(ledger.ApplicationID().Address(), 999+2*defaultEvalProto().MinTxnFee) + ledger.NewAccount(appAddr(888), 999+2*MakeTestProto().MinTxnFee) pay := ` int pay; itxn_field TypeEnum; int 500; itxn_field Amount; txn Sender; itxn_field Receiver; ` - testApp(t, "itxn_begin"+pay+"itxn_next"+pay+"itxn_submit; int 1", ep, + TestApp(t, "itxn_begin"+pay+"itxn_next"+pay+"itxn_submit; int 1", ep, "insufficient balance") // NewAccount overwrites the existing balance - ledger.NewAccount(ledger.ApplicationID().Address(), 1000+2*defaultEvalProto().MinTxnFee) - testApp(t, "itxn_begin"+pay+"itxn_next"+pay+"itxn_submit; int 1", ep) + ledger.NewAccount(appAddr(888), 1000+2*MakeTestProto().MinTxnFee) + TestApp(t, "itxn_begin"+pay+"itxn_next"+pay+"itxn_submit; int 1", ep) } func TestInnerFeePooling(t *testing.T) { - ep, ledger := makeSampleEnv() - ledger.NewApp(ep.Txn.Txn.Receiver, 888, basics.AppParams{}) - ledger.NewAccount(ledger.ApplicationID().Address(), 50_000) + ep, tx, ledger := MakeSampleEnv() + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + ledger.NewAccount(appAddr(888), 50_000) pay := ` int pay; itxn_field TypeEnum; int 500; itxn_field Amount; txn Sender; itxn_field Receiver; ` // Force the first fee to 3, but the second will default to 2*fee-3 = 2002-3 - testApp(t, "itxn_begin"+ + TestApp(t, "itxn_begin"+ pay+ "int 3; itxn_field Fee;"+ "itxn_next"+ @@ -555,7 +623,7 @@ txn Sender; itxn_field Receiver; "itxn_submit; itxn Fee; int 1999; ==", ep) // Same first, but force the second too low - testApp(t, "itxn_begin"+ + TestApp(t, "itxn_begin"+ pay+ "int 3; itxn_field Fee;"+ "itxn_next"+ @@ -564,7 +632,7 @@ txn Sender; itxn_field Receiver; "itxn_submit; int 1", ep, "fee too small") // Overpay in first itxn, the second will default to less - testApp(t, "itxn_begin"+ + TestApp(t, "itxn_begin"+ pay+ "int 2000; itxn_field Fee;"+ "itxn_next"+ @@ -572,7 +640,7 @@ txn Sender; itxn_field Receiver; "itxn_submit; itxn Fee; int 2; ==", ep) // Same first, but force the second too low - testApp(t, "itxn_begin"+ + TestApp(t, "itxn_begin"+ pay+ "int 2000; itxn_field Fee;"+ "itxn_next"+ @@ -580,3 +648,958 @@ txn Sender; itxn_field Receiver; "int 1; itxn_field Fee;"+ "itxn_submit; itxn Fee; int 1", ep, "fee too small") } + +// TestApplCreation is only determining what appl transactions can be +// constructed not what can be submitted, so it tests what "bad" fields cause +// immediate failures. +func TestApplCreation(t *testing.T) { + ep, tx, _ := MakeSampleEnv() + + p := "itxn_begin;" + s := "; int 1" + + TestApp(t, p+"int 31; itxn_field ApplicationID"+s, ep, + "invalid App reference") + tx.ForeignApps = append(tx.ForeignApps, 31) + TestApp(t, p+"int 31; itxn_field ApplicationID"+s, ep) + + TestApp(t, p+"int 0; itxn_field OnCompletion"+s, ep) + TestApp(t, p+"int 1; itxn_field OnCompletion"+s, ep) + TestApp(t, p+"int 2; itxn_field OnCompletion"+s, ep) + TestApp(t, p+"int 3; itxn_field OnCompletion"+s, ep) + TestApp(t, p+"int 4; itxn_field OnCompletion"+s, ep) + TestApp(t, p+"int 5; itxn_field OnCompletion"+s, ep) + TestApp(t, p+"int 6; itxn_field OnCompletion"+s, ep, "6 is larger than max=5") + TestApp(t, p+"int NoOp; itxn_field OnCompletion"+s, ep) + TestApp(t, p+"int OptIn; itxn_field OnCompletion"+s, ep) + TestApp(t, p+"int CloseOut; itxn_field OnCompletion"+s, ep) + TestApp(t, p+"int ClearState; itxn_field OnCompletion"+s, ep) + TestApp(t, p+"int UpdateApplication; itxn_field OnCompletion"+s, ep) + TestApp(t, p+"int DeleteApplication; itxn_field OnCompletion"+s, ep) + + TestApp(t, p+"int 800; bzero; itxn_field ApplicationArgs"+s, ep) + TestApp(t, p+"int 801; bzero; itxn_field ApplicationArgs", ep, + "length too long") + TestApp(t, p+"int 401; bzero; dup; itxn_field ApplicationArgs; itxn_field ApplicationArgs", ep, + "length too long") + + TestApp(t, p+strings.Repeat("byte 0x11; itxn_field ApplicationArgs;", 12)+s, ep) + TestApp(t, p+strings.Repeat("byte 0x11; itxn_field ApplicationArgs;", 13)+s, ep, + "too many application args") + + TestApp(t, p+strings.Repeat("int 32; bzero; itxn_field Accounts;", 3)+s, ep, + "invalid Account reference") + tx.Accounts = append(tx.Accounts, basics.Address{}) + TestApp(t, fmt.Sprintf(p+"%s"+s, + strings.Repeat("int 32; bzero; itxn_field Accounts;", 3)), ep) + TestApp(t, fmt.Sprintf(p+"%s"+s, + strings.Repeat("int 32; bzero; itxn_field Accounts;", 4)), ep, + "too many foreign accounts") + + TestApp(t, p+strings.Repeat("int 621; itxn_field Applications;", 5)+s, ep, + "invalid App reference") + tx.ForeignApps = append(tx.ForeignApps, basics.AppIndex(621)) + TestApp(t, p+strings.Repeat("int 621; itxn_field Applications;", 5)+s, ep) + TestApp(t, p+strings.Repeat("int 621; itxn_field Applications;", 6)+s, ep, + "too many foreign apps") + + TestApp(t, p+strings.Repeat("int 621; itxn_field Assets;", 6)+s, ep, + "invalid Asset reference") + tx.ForeignAssets = append(tx.ForeignAssets, basics.AssetIndex(621)) + TestApp(t, p+strings.Repeat("int 621; itxn_field Assets;", 6)+s, ep) + TestApp(t, p+strings.Repeat("int 621; itxn_field Assets;", 7)+s, ep, + "too many foreign assets") + + TestApp(t, p+"int 2700; bzero; itxn_field ApprovalProgram"+s, ep) + TestApp(t, p+"int 2701; bzero; itxn_field ApprovalProgram"+s, ep, + "may not exceed 2700") + TestApp(t, p+"int 2700; bzero; itxn_field ClearStateProgram"+s, ep) + TestApp(t, p+"int 2701; bzero; itxn_field ClearStateProgram"+s, ep, + "may not exceed 2700") + + TestApp(t, p+"int 30; itxn_field GlobalNumUint"+s, ep) + TestApp(t, p+"int 31; itxn_field GlobalNumUint"+s, ep, "31 is larger than max=30") + TestApp(t, p+"int 30; itxn_field GlobalNumByteSlice"+s, ep) + TestApp(t, p+"int 31; itxn_field GlobalNumByteSlice"+s, ep, "31 is larger than max=30") + TestApp(t, p+"int 20; itxn_field GlobalNumUint; int 11; itxn_field GlobalNumByteSlice"+s, ep) + + TestApp(t, p+"int 13; itxn_field LocalNumUint"+s, ep) + TestApp(t, p+"int 14; itxn_field LocalNumUint"+s, ep, "14 is larger than max=13") + TestApp(t, p+"int 13; itxn_field LocalNumByteSlice"+s, ep) + TestApp(t, p+"int 14; itxn_field LocalNumByteSlice"+s, ep, "14 is larger than max=13") + + TestApp(t, p+"int 2; itxn_field ExtraProgramPages"+s, ep) + TestApp(t, p+"int 3; itxn_field ExtraProgramPages"+s, ep, "3 is larger than max=2") +} + +// TestApplSubmission tests for checking of illegal appl transaction in form +// only. Things where interactions between two different fields causes the +// error. These are not exhaustive, but certainly demonstrate that WellFormed +// is getting a crack at the txn. +func TestApplSubmission(t *testing.T) { + ep, tx, ledger := MakeSampleEnv() + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + // Since the fee is moved first, fund the app + ledger.NewAccount(appAddr(888), 50_000) + + ops := TestProg(t, "int 1", AssemblerMaxVersion) + approve := hex.EncodeToString(ops.Program) + a := fmt.Sprintf("byte 0x%s; itxn_field ApprovalProgram;", approve) + + p := "itxn_begin; int appl; itxn_field TypeEnum;" + s := ";itxn_submit; int 1" + TestApp(t, p+a+s, ep) + + // All zeros is v0, so we get a complaint, but that means lengths were ok. + TestApp(t, p+a+`int 600; bzero; itxn_field ApprovalProgram; + int 600; bzero; itxn_field ClearStateProgram;`+s, ep, + "program version must be") + + TestApp(t, p+`int 601; bzero; itxn_field ApprovalProgram; + int 600; bzero; itxn_field ClearStateProgram;`+s, ep, "too long") + + // WellFormed does the math based on the supplied ExtraProgramPages + TestApp(t, p+a+`int 1; itxn_field ExtraProgramPages + int 1200; bzero; itxn_field ApprovalProgram; + int 1200; bzero; itxn_field ClearStateProgram;`+s, ep, + "program version must be") + TestApp(t, p+`int 1; itxn_field ExtraProgramPages + int 1200; bzero; itxn_field ApprovalProgram; + int 1201; bzero; itxn_field ClearStateProgram;`+s, ep, "too long") + + // Can't set epp when app id is given + tx.ForeignApps = append(tx.ForeignApps, basics.AppIndex(7)) + TestApp(t, p+`int 1; itxn_field ExtraProgramPages; + int 7; itxn_field ApplicationID`+s, ep, "immutable") + + TestApp(t, p+"int 20; itxn_field GlobalNumUint; int 11; itxn_field GlobalNumByteSlice"+s, + ep, "too large") + TestApp(t, p+"int 7; itxn_field LocalNumUint; int 7; itxn_field LocalNumByteSlice"+s, + ep, "too large") +} + +func TestInnerApplCreate(t *testing.T) { + ep, tx, ledger := MakeSampleEnv() + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + ledger.NewAccount(appAddr(888), 50_000) + + ops := TestProg(t, "int 1", AssemblerMaxVersion) + approve := "byte 0x" + hex.EncodeToString(ops.Program) + + TestApp(t, ` +itxn_begin +int appl; itxn_field TypeEnum +`+approve+`; itxn_field ApprovalProgram +`+approve+`; itxn_field ClearStateProgram +int 1; itxn_field GlobalNumUint +int 2; itxn_field LocalNumByteSlice +int 3; itxn_field LocalNumUint +itxn_submit +int 1 +`, ep) + + TestApp(t, ` +int 5000; app_params_get AppGlobalNumByteSlice; assert; int 0; ==; assert +`, ep, "invalid App reference") + + call := ` +itxn_begin +int appl; itxn_field TypeEnum +int 5000; itxn_field ApplicationID +itxn_submit +int 1 +` + // Can't call it either + TestApp(t, call, ep, "invalid App reference") + + tx.ForeignApps = []basics.AppIndex{basics.AppIndex(5000)} + TestApp(t, ` +int 5000; app_params_get AppGlobalNumByteSlice; assert; int 0; ==; assert +int 5000; app_params_get AppGlobalNumUint; assert; int 1; ==; assert +int 5000; app_params_get AppLocalNumByteSlice; assert; int 2; ==; assert +int 5000; app_params_get AppLocalNumUint; assert; int 3; ==; assert +int 1 +`, ep) + + // Call it (default OnComplete is NoOp) + TestApp(t, call, ep) + + TestApp(t, ` +itxn_begin +int appl; itxn_field TypeEnum +int DeleteApplication; itxn_field OnCompletion +txn Applications 1; itxn_field ApplicationID +itxn_submit +int 1 +`, ep) + + // App is gone + TestApp(t, ` +int 5000; app_params_get AppGlobalNumByteSlice; !; assert; !; assert; int 1 +`, ep) + + // Can't call it either + TestApp(t, call, ep, "No application") + +} + +func TestCreateOldAppFails(t *testing.T) { + ep, tx, ledger := MakeSampleEnv() + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + ledger.NewAccount(appAddr(888), 50_000) + + ops := TestProg(t, "int 1", InnerAppsEnabledVersion-1) + approve := "byte 0x" + hex.EncodeToString(ops.Program) + + TestApp(t, ` +itxn_begin +int appl; itxn_field TypeEnum +`+approve+`; itxn_field ApprovalProgram +`+approve+`; itxn_field ClearStateProgram +int 1; itxn_field GlobalNumUint +int 2; itxn_field LocalNumByteSlice +int 3; itxn_field LocalNumUint +itxn_submit +int 1 +`, ep, "program version must be >=") +} + +func TestSelfReentrancy(t *testing.T) { + ep, tx, ledger := MakeSampleEnv() + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + ledger.NewAccount(appAddr(888), 50_000) + + TestApp(t, ` +itxn_begin +int appl; itxn_field TypeEnum +int 888; itxn_field ApplicationID +itxn_submit +int 1 +`, ep, "attempt to self-call") +} + +func TestIndirectReentrancy(t *testing.T) { + ep, tx, ledger := MakeSampleEnv() + call888 := TestProg(t, `itxn_begin +int appl; itxn_field TypeEnum +int 888; itxn_field ApplicationID +itxn_submit +int 1 +`, AssemblerMaxVersion) + ledger.NewApp(tx.Receiver, 222, basics.AppParams{ + ApprovalProgram: call888.Program, + }) + + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + ledger.NewAccount(appAddr(888), 50_000) + tx.ForeignApps = []basics.AppIndex{basics.AppIndex(222)} + TestApp(t, ` +itxn_begin +int appl; itxn_field TypeEnum +int 222; itxn_field ApplicationID +int 888; itxn_field Applications +itxn_submit +int 1 +`, ep, "attempt to re-enter 888") +} + +// TestInnerAppID ensures that inner app properly sees its AppId. This seems +// needlessly picky to test, but the appID used to be stored outside the cx. +func TestInnerAppID(t *testing.T) { + ep, tx, ledger := MakeSampleEnv() + logID := TestProg(t, `global CurrentApplicationID; itob; log; int 1`, AssemblerMaxVersion) + ledger.NewApp(tx.Receiver, 222, basics.AppParams{ + ApprovalProgram: logID.Program, + }) + + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + ledger.NewAccount(appAddr(888), 50_000) + tx.ForeignApps = []basics.AppIndex{basics.AppIndex(222)} + TestApp(t, ` +itxn_begin +int appl; itxn_field TypeEnum +int 222; itxn_field ApplicationID +itxn_submit +itxn Logs 0 +btoi +int 222 +== +`, ep) +} + +// TestInnerBudgetIncrement ensures that an app can make a (nearly) empty inner +// app call in order to get 700 extra opcode budget. Unfortunately, it costs a +// bit to create the call, and the app itself consumes 1, so it ends up being +// about 690 (see next test). +func TestInnerBudgetIncrement(t *testing.T) { + ep, tx, ledger := MakeSampleEnv() + gasup := TestProg(t, "pushint 1", AssemblerMaxVersion) + ledger.NewApp(tx.Receiver, 222, basics.AppParams{ + ApprovalProgram: gasup.Program, + }) + + waste := `global CurrentApplicationAddress; keccak256; pop;` + buy := `itxn_begin +int appl; itxn_field TypeEnum +int 222; itxn_field ApplicationID +itxn_submit; +` + + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + ledger.NewAccount(appAddr(888), 50_000) + tx.ForeignApps = []basics.AppIndex{basics.AppIndex(222)} + TestApp(t, strings.Repeat(waste, 5)+"int 1", ep) + TestApp(t, strings.Repeat(waste, 6)+"int 1", ep, "dynamic cost budget exceeded") + TestApp(t, strings.Repeat(waste, 6)+buy+"int 1", ep, "dynamic cost budget exceeded") + TestApp(t, buy+strings.Repeat(waste, 6)+"int 1", ep) + TestApp(t, buy+strings.Repeat(waste, 10)+"int 1", ep) + TestApp(t, buy+strings.Repeat(waste, 12)+"int 1", ep, "dynamic cost budget exceeded") + TestApp(t, buy+strings.Repeat(waste, 12)+"int 1", ep, "dynamic cost budget exceeded") + TestApp(t, buy+buy+strings.Repeat(waste, 12)+"int 1", ep) +} + +func TestIncrementCheck(t *testing.T) { + ep, tx, ledger := MakeSampleEnv() + gasup := TestProg(t, "pushint 1", AssemblerMaxVersion) + ledger.NewApp(tx.Receiver, 222, basics.AppParams{ + ApprovalProgram: gasup.Program, + }) + + source := ` +// 698, not 699, because intcblock happens first +global OpcodeBudget; int 698; ==; assert +// "buy" more +itxn_begin +int appl; itxn_field TypeEnum +int 222; itxn_field ApplicationID +itxn_submit; +global OpcodeBudget; int 1387; ==; assert +itxn_begin +int appl; itxn_field TypeEnum +int 222; itxn_field ApplicationID +itxn_submit; +global OpcodeBudget; int 2076; ==; assert +int 1 +` + + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + ledger.NewAccount(appAddr(888), 50_000) + tx.ForeignApps = []basics.AppIndex{basics.AppIndex(222)} + TestApp(t, source, ep) +} + +// TestInnerTxIDs confirms that TxIDs are available and different +func TestInnerTxIDs(t *testing.T) { + ep, tx, ledger := MakeSampleEnv() + txid := TestProg(t, "txn TxID; log; int 1", AssemblerMaxVersion) + ledger.NewApp(tx.Receiver, 222, basics.AppParams{ + ApprovalProgram: txid.Program, + }) + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + ledger.NewAccount(appAddr(888), 50_000) + tx.ForeignApps = []basics.AppIndex{basics.AppIndex(222)} + TestApp(t, ` +itxn_begin +int appl; itxn_field TypeEnum +int 222; itxn_field ApplicationID +itxn_submit; +itxn Logs 0 + +itxn_begin +int appl; itxn_field TypeEnum +int 222; itxn_field ApplicationID +itxn_submit; +itxn Logs 0 + +!= +`, ep) +} + +// TestInnerGroupIDs confirms that GroupIDs are unset on size one inner groups, +// but set and unique on non-singletons +func TestInnerGroupIDs(t *testing.T) { + ep, tx, ledger := MakeSampleEnv() + gid := TestProg(t, "global GroupID; log; int 1", AssemblerMaxVersion) + ledger.NewApp(tx.Receiver, 222, basics.AppParams{ + ApprovalProgram: gid.Program, + }) + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + ledger.NewAccount(appAddr(888), 50_000) + tx.ForeignApps = []basics.AppIndex{basics.AppIndex(222)} + + // A single txn gets 0 group id + TestApp(t, ` +itxn_begin +int appl; itxn_field TypeEnum +int 222; itxn_field ApplicationID +itxn_submit; +itxn Logs 0 +global ZeroAddress +== +`, ep) + + // A double calls gets something else + TestApp(t, ` +itxn_begin +int appl; itxn_field TypeEnum +int 222; itxn_field ApplicationID +itxn_next +int appl; itxn_field TypeEnum +int 222; itxn_field ApplicationID +itxn_submit; +itxn Logs 0 +global ZeroAddress +!= +`, ep) + + // The "something else" is unique, despite two identical groups + TestApp(t, ` +itxn_begin +int appl; itxn_field TypeEnum +int 222; itxn_field ApplicationID +itxn_next +int appl; itxn_field TypeEnum +int 222; itxn_field ApplicationID +itxn_submit; +itxn Logs 0 + +itxn_begin +int appl; itxn_field TypeEnum +int 222; itxn_field ApplicationID +itxn_next +int appl; itxn_field TypeEnum +int 222; itxn_field ApplicationID +itxn_submit; +itxn Logs 0 + +!= +`, ep) +} + +// TestGtixn confirms access to itxn groups +func TestGtixn(t *testing.T) { + ep, tx, ledger := MakeSampleEnv() + two := TestProg(t, "byte 0x22; log; int 1", AssemblerMaxVersion) + ledger.NewApp(tx.Receiver, 222, basics.AppParams{ + ApprovalProgram: two.Program, + }) + three := TestProg(t, "byte 0x33; log; int 1", AssemblerMaxVersion) + ledger.NewApp(tx.Receiver, 333, basics.AppParams{ + ApprovalProgram: three.Program, + }) + four := TestProg(t, "byte 0x44; log; int 1", AssemblerMaxVersion) + ledger.NewApp(tx.Receiver, 444, basics.AppParams{ + ApprovalProgram: four.Program, + }) + + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + ledger.NewAccount(appAddr(888), 50_000) + tx.ForeignApps = []basics.AppIndex{basics.AppIndex(222), basics.AppIndex(333), basics.AppIndex(444)} + + TestApp(t, ` +itxn_begin +int appl; itxn_field TypeEnum +int 222; itxn_field ApplicationID +itxn_next +int appl; itxn_field TypeEnum +int 333; itxn_field ApplicationID +itxn_submit; +gitxn 0 Logs 0 +byte 0x22 +== +assert + +gitxna 1 Logs 0 +byte 0x33 +== +assert + +itxn_begin +int appl; itxn_field TypeEnum +int 444; itxn_field ApplicationID +itxn_next +int appl; itxn_field TypeEnum +int 222; itxn_field ApplicationID +itxn_submit; + +gitxn 0 Logs 0 +byte 0x44 +== +assert + +gitxn 1 Logs 0 +byte 0x22 +== +assert + +int 1 +`, ep) + + // Confirm that two singletons don't get treated as a group + TestApp(t, ` +itxn_begin +int appl; itxn_field TypeEnum +int 222; itxn_field ApplicationID +itxn_submit + +itxn_begin +int appl; itxn_field TypeEnum +int 333; itxn_field ApplicationID +itxn_submit +gitxn 0 Logs 0 +byte 0x33 +== +assert +int 1 +`, ep) +} + +// TestGtxnLog confirms that gtxn can now access previous txn's Logs. +func TestGtxnLog(t *testing.T) { + ep, tx, ledger := MakeSampleEnv() + two := TestProg(t, "byte 0x22; log; int 1", AssemblerMaxVersion) + ledger.NewApp(tx.Receiver, 222, basics.AppParams{ + ApprovalProgram: two.Program, + }) + three := TestProg(t, "gtxn 0 NumLogs; int 1; ==; assert; gtxna 0 Logs 0; byte 0x22; ==", AssemblerMaxVersion) + ledger.NewApp(tx.Receiver, 333, basics.AppParams{ + ApprovalProgram: three.Program, + }) + + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + ledger.NewAccount(appAddr(888), 50_000) + tx.ForeignApps = []basics.AppIndex{basics.AppIndex(222), basics.AppIndex(333)} + + TestApp(t, `itxn_begin +int appl; itxn_field TypeEnum +int 222; itxn_field ApplicationID +itxn_next +int appl; itxn_field TypeEnum +int 333; itxn_field ApplicationID +itxn_submit +int 1 +`, ep) +} + +// TestGtxnApps confirms that gtxn can now access previous txn's created app id. +func TestGtxnApps(t *testing.T) { + ep, tx, ledger := MakeSampleEnv() + appcheck := TestProg(t, ` +gtxn 0 CreatedApplicationID; itob; log; +gtxn 1 CreatedApplicationID; itob; log; +int 1 +`, AssemblerMaxVersion) + ledger.NewApp(tx.Receiver, 222, basics.AppParams{ + ApprovalProgram: appcheck.Program, + }) + + ops := TestProg(t, "int 1", AssemblerMaxVersion) + + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + ledger.NewAccount(appAddr(888), 50_000) + tx.ForeignApps = []basics.AppIndex{basics.AppIndex(222)} + TestApp(t, `itxn_begin +int appl; itxn_field TypeEnum + `+fmt.Sprintf("byte 0x%s; itxn_field ApprovalProgram;", hex.EncodeToString(ops.Program))+` +itxn_next +int appl; itxn_field TypeEnum + `+fmt.Sprintf("byte 0x%s; itxn_field ApprovalProgram;", hex.EncodeToString(ops.Program))+` +itxn_next +int appl; itxn_field TypeEnum +int 222; itxn_field ApplicationID +itxn_submit +itxn Logs 0 +btoi +int 5000 +== +assert +gitxn 2 Logs 1 +btoi +int 5001 +== +`, ep) +} + +// TestGtxnAsa confirms that gtxn can now access previous txn's created asa id. +func TestGtxnAsa(t *testing.T) { + ep, tx, ledger := MakeSampleEnv() + appcheck := TestProg(t, ` +gtxn 0 CreatedAssetID; itob; log; +gtxn 1 CreatedAssetID; itob; log; +int 1 +`, AssemblerMaxVersion) + ledger.NewApp(tx.Receiver, 222, basics.AppParams{ + ApprovalProgram: appcheck.Program, + }) + + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + ledger.NewAccount(appAddr(888), 50_000) + tx.ForeignApps = []basics.AppIndex{basics.AppIndex(222)} + TestApp(t, `itxn_begin +int acfg; itxn_field TypeEnum +itxn_next +int acfg; itxn_field TypeEnum +itxn_next +int appl; itxn_field TypeEnum +int 222; itxn_field ApplicationID +itxn_submit +itxn Logs 0 +btoi +int 5000 +== +assert +gitxn 2 Logs 1 +btoi +int 5001 +== +`, ep) +} + +// TestCallerGlobals checks that a called app can see its caller. +func TestCallerGlobals(t *testing.T) { + ep, tx, ledger := MakeSampleEnv() + globals := TestProg(t, fmt.Sprintf(` +global CallerApplicationID +int 888 +== +global CallerApplicationAddress +addr %s +== +&& +`, basics.AppIndex(888).Address()), AssemblerMaxVersion) + ledger.NewApp(tx.Receiver, 222, basics.AppParams{ + ApprovalProgram: globals.Program, + }) + + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + ledger.NewAccount(appAddr(888), 50_000) + tx.ForeignApps = []basics.AppIndex{basics.AppIndex(222)} + TestApp(t, `itxn_begin +int appl; itxn_field TypeEnum +int 222; itxn_field ApplicationID +itxn_submit +int 1 +`, ep) +} + +// TestNumInnerDeep ensures that inner call limits apply to inner calls of inner +// transactions. +func TestNumInnerDeep(t *testing.T) { + pay := ` + itxn_begin + int 1 + itxn_field Amount + txn Accounts 0 + itxn_field Receiver + int pay + itxn_field TypeEnum + itxn_submit +` + + tx := txntest.Txn{ + Type: protocol.ApplicationCallTx, + ApplicationID: 888, + ForeignApps: []basics.AppIndex{basics.AppIndex(222)}, + }.SignedTxnWithAD() + require.Equal(t, 888, int(tx.Txn.ApplicationID)) + ledger := MakeLedger(nil) + + pay3 := TestProg(t, pay+pay+pay+"int 1;", AssemblerMaxVersion).Program + ledger.NewApp(tx.Txn.Receiver, 222, basics.AppParams{ + ApprovalProgram: pay3, + }) + + ledger.NewApp(tx.Txn.Receiver, 888, basics.AppParams{}) + ledger.NewAccount(appAddr(888), 1_000_000) + + callpay3 := `itxn_begin +int appl; itxn_field TypeEnum +int 222; itxn_field ApplicationID +itxn_submit +` + txg := []transactions.SignedTxnWithAD{tx} + ep := NewEvalParams(txg, MakeTestProto(), &transactions.SpecialAddresses{}) + ep.Ledger = ledger + TestApp(t, callpay3+"int 1", ep, "insufficient balance") // inner contract needs money + + ledger.NewAccount(appAddr(222), 1_000_000) + TestApp(t, callpay3+"int 1", ep) + // Each use of callpay3 is 4 inners total, so 8 is ok. (32 allowed in test ep) + TestApp(t, strings.Repeat(callpay3, 8)+"int 1", ep) + TestApp(t, strings.Repeat(callpay3, 9)+"int 1", ep, "too many inner transactions") +} + +// TestCreateAndUse checks that an ASA can be created in an inner app, and then +// used. This was not allowed until v6, because of the strict adherence to the +// foreign-arrays rules. +func TestCreateAndUse(t *testing.T) { + axfer := ` + itxn_begin + int acfg; itxn_field TypeEnum + int 10; itxn_field ConfigAssetTotal + byte "Gold"; itxn_field ConfigAssetName + itxn_submit + + itxn_begin + int axfer; itxn_field TypeEnum + itxn CreatedAssetID; itxn_field XferAsset + txn Accounts 0; itxn_field AssetReceiver + itxn_submit + + int 1 +` + + // First testing use in axfer + ep, tx, ledger := MakeSampleEnv() + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + ledger.NewAccount(appAddr(888), 4*MakeTestProto().MinTxnFee) + TestApp(t, axfer, ep) + + ep.Proto = MakeTestProtoV(CreatedResourcesVersion - 1) + TestApp(t, axfer, ep, "invalid Asset reference") + + balance := ` + itxn_begin + int acfg; itxn_field TypeEnum + int 10; itxn_field ConfigAssetTotal + byte "Gold"; itxn_field ConfigAssetName + itxn_submit + + // txn Sender is not opted-in, as it's the app account that made the asset + // At some point, we should short-circuit so this does not go to disk. + txn Sender + itxn CreatedAssetID + asset_holding_get AssetBalance + int 0 + == + assert + int 0 + == + assert + + // App account owns all the newly made gold + global CurrentApplicationAddress + itxn CreatedAssetID + asset_holding_get AssetBalance + assert + int 10 + == + assert + + int 1 +` + + // Now test use in asset balance opcode + ep, tx, ledger = MakeSampleEnv() + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + ledger.NewAccount(appAddr(888), 4*MakeTestProto().MinTxnFee) + TestApp(t, balance, ep) + + ep.Proto = MakeTestProtoV(CreatedResourcesVersion - 1) + TestApp(t, balance, ep, "invalid Asset reference") + + appcall := ` + itxn_begin + int acfg; itxn_field TypeEnum + int 10; itxn_field ConfigAssetTotal + byte "Gold"; itxn_field ConfigAssetName + itxn_submit + + itxn_begin + int appl; itxn_field TypeEnum + int 888; itxn_field ApplicationID + itxn CreatedAssetID; itxn_field Assets + itxn_submit + + int 1 +` + + // Now as ForeignAsset + ep, tx, ledger = MakeSampleEnv() + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + ledger.NewAccount(appAddr(888), 4*MakeTestProto().MinTxnFee) + // It gets passed the Assets setting + TestApp(t, appcall, ep, "attempt to self-call") + + // Appcall is isn't allowed pre-CreatedResourcesVersion, because same + // version allowed inner app calls + // ep.Proto = MakeTestProtoV(CreatedResourcesVersion - 1) + // TestApp(t, appcall, ep, "invalid Asset reference") +} + +// main wraps up some TEAL source in a header and footer so that it is +// an app that does nothing at create time, but otherwise runs source, +// then approves, if the source avoids panicing and leaves the stack +// empty. +func main(source string) string { + return fmt.Sprintf(`txn ApplicationID + bz end + %s + end: int 1`, source) +} + +func hexProgram(t *testing.T, source string) string { + return "0x" + hex.EncodeToString(TestProg(t, source, AssemblerMaxVersion).Program) +} + +// TestCreateAndUseApp checks that an app can be created in an inner txn, and then +// the address for it can be looked up. +func TestCreateUseApp(t *testing.T) { + pay5back := main(` +itxn_begin +int pay; itxn_field TypeEnum +txn Sender; itxn_field Receiver +int 5; itxn_field Amount +itxn_submit +int 1 +`) + + createAndUse := ` + itxn_begin + int appl; itxn_field TypeEnum + byte ` + hexProgram(t, pay5back) + `; itxn_field ApprovalProgram; + itxn_submit + + itxn CreatedApplicationID; app_params_get AppAddress; assert + addr ` + appAddr(5000).String() + ` + == +` + + ep, tx, ledger := MakeSampleEnv() + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + ledger.NewAccount(appAddr(888), 1*MakeTestProto().MinTxnFee) + TestApp(t, createAndUse, ep) + // Again, can't test if this (properly) fails in previous version, because + // we can't even create apps this way in previous version. +} + +// TestCreateAndPay checks that an app can be created in an inner app, and then +// a pay can be done to the app's account. This was not allowed until v6, +// because of the strict adherence to the foreign-accounts rules. +func TestCreateAndPay(t *testing.T) { + pay5back := main(` +itxn_begin +int pay; itxn_field TypeEnum +txn Sender; itxn_field Receiver +int 5; itxn_field Amount +itxn_submit +int 1 +`) + + createAndPay := ` + itxn_begin + int appl; itxn_field TypeEnum + ` + fmt.Sprintf("byte %s; itxn_field ApprovalProgram;", hexProgram(t, pay5back)) + ` + itxn_submit + + itxn_begin + int pay; itxn_field TypeEnum + itxn CreatedApplicationID; app_params_get AppAddress; assert; itxn_field Receiver + int 10; itxn_field Amount + itxn_submit + + int 1 +` + + ep, tx, ledger := MakeSampleEnv() + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + ledger.NewAccount(appAddr(888), 10*MakeTestProto().MinTxnFee) + TestApp(t, createAndPay, ep) + + // This test is impossible because CreatedResourcesVersion is also when + // inner txns could make apps. + // ep.Proto = MakeTestProtoV(CreatedResourcesVersion - 1) + // TestApp(t, createAndPay, ep, "invalid Address reference") +} + +// TestInnerGaid ensures there's no confusion over the tracking of ids +// across multiple inner transaction groups +func TestInnerGaid(t *testing.T) { + ep, tx, ledger := MakeSampleEnv() + ep.Proto.MaxInnerTransactions = 100 + // App to log the aid of slot[apparg[0]] + logGaid := TestProg(t, `txn ApplicationArgs 0; btoi; gaids; itob; log; int 1`, AssemblerMaxVersion) + ledger.NewApp(tx.Receiver, 222, basics.AppParams{ + ApprovalProgram: logGaid.Program, + }) + + ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) + ledger.NewAccount(appAddr(888), 50_000) + tx.ForeignApps = []basics.AppIndex{basics.AppIndex(222)} + TestApp(t, `itxn_begin +int acfg; itxn_field TypeEnum +itxn_next +int pay; itxn_field TypeEnum +txn Sender; itxn_field Receiver +itxn_next +int appl; itxn_field TypeEnum +int 222; itxn_field ApplicationID +int 0; itob; itxn_field ApplicationArgs +itxn_submit +itxn Logs 0 +btoi +int 5000 +== +assert + +// Swap the pay and acfg, ensure gaid 1 works instead +itxn_begin +int pay; itxn_field TypeEnum +txn Sender; itxn_field Receiver +itxn_next +int acfg; itxn_field TypeEnum +itxn_next +int appl; itxn_field TypeEnum +int 222; itxn_field ApplicationID +int 1; itob; itxn_field ApplicationArgs +itxn_submit +itxn Logs 0 +btoi +int 5001 +== +assert + + +int 1 +`, ep) + + // Nearly identical, but ensures that gaid 0 FAILS in the second group + TestApp(t, `itxn_begin +int acfg; itxn_field TypeEnum +itxn_next +int pay; itxn_field TypeEnum +txn Sender; itxn_field Receiver +itxn_next +int appl; itxn_field TypeEnum +int 222; itxn_field ApplicationID +int 0; itob; itxn_field ApplicationArgs +itxn_submit +itxn Logs 0 +btoi +int 5000 +== +assert + +// Swap the pay and acfg, ensure gaid 1 works instead +itxn_begin +int pay; itxn_field TypeEnum +txn Sender; itxn_field Receiver +itxn_next +int acfg; itxn_field TypeEnum +itxn_next +int appl; itxn_field TypeEnum +int 222; itxn_field ApplicationID +int 0; itob; itxn_field ApplicationArgs +itxn_submit +itxn Logs 0 +btoi +int 5001 +== +assert + + +int 1 +`, ep, "assert failed") + +} |