diff --git a/data/transactions/logic/README.md b/data/transactions/logic/README.md index 324cd7d536..6af976489e 100644 --- a/data/transactions/logic/README.md +++ b/data/transactions/logic/README.md @@ -193,6 +193,40 @@ _available_. * Since v7, the account associated with any contract present in the `txn.ForeignApplications` field is _available_. + + * Since v9, there is group-level resource sharing. Any resource that + is available in _some_ top-level transaction in a transaction group + is available in _all_ v9 or later application calls in the group, + whether those application calls are top-level or inner. + + * When considering whether an asset holding or application local + state is available by group-level resource sharing, the holding or + local state must be available in a top-level transaction without + considering group sharing. For example, if account A is made + available in one transaction, and asset X is made available in + another, group resource sharing does _not_ make A's X holding + available. + + * Top-level transactions that are not application calls also make + resources available to group-level resource sharing. The following + resources are made available other transaction types. + + 1. `pay` - `txn.Sender`, `txn.Receiver`, and `txn.CloseRemainderTo` + + 1. `keyreg` - `txn.Sender` + + 1. `acfg` - `txn.Sender`, `txn.ConfigAsset`, and the + `txn.ConfigAsset` holding of `txn.Sender`. + + 1. `axfer` - `txn.Sender`, `txn.AssetSender`, `txnAssetCloseTo`, + `txn.XferAsset` and the `txn.XferAsset` holding of each of + those accounts. + + 1. `afrz` - `txn.Sender`, `txn.FreezeAccount`, `txn.FreezeAsset`, + and the `txn.FreezeAsset` holding of `txn.FreezeAccount`. The + `txn.FreezeAsset` holding of `txn.Sender` is _not_ made + available. + * A Box is _available_ to an Approval Program if _any_ transaction in the same group contains a box reference (`txn.Boxes`) that denotes diff --git a/data/transactions/logic/README_in.md b/data/transactions/logic/README_in.md index 58652ebcf7..9c0e9ec4d9 100644 --- a/data/transactions/logic/README_in.md +++ b/data/transactions/logic/README_in.md @@ -193,6 +193,40 @@ _available_. * Since v7, the account associated with any contract present in the `txn.ForeignApplications` field is _available_. + + * Since v9, there is group-level resource sharing. Any resource that + is available in _some_ top-level transaction in a transaction group + is available in _all_ v9 or later application calls in the group, + whether those application calls are top-level or inner. + + * When considering whether an asset holding or application local + state is available by group-level resource sharing, the holding or + local state must be available in a top-level transaction without + considering group sharing. For example, if account A is made + available in one transaction, and asset X is made available in + another, group resource sharing does _not_ make A's X holding + available. + + * Top-level transactions that are not application calls also make + resources available to group-level resource sharing. The following + resources are made available other transaction types. + + 1. `pay` - `txn.Sender`, `txn.Receiver`, and `txn.CloseRemainderTo` + + 1. `keyreg` - `txn.Sender` + + 1. `acfg` - `txn.Sender`, `txn.ConfigAsset`, and the + `txn.ConfigAsset` holding of `txn.Sender`. + + 1. `axfer` - `txn.Sender`, `txn.AssetSender`, `txnAssetCloseTo`, + `txn.XferAsset` and the `txn.XferAsset` holding of each of + those accounts. + + 1. `afrz` - `txn.Sender`, `txn.FreezeAccount`, `txn.FreezeAsset`, + and the `txn.FreezeAsset` holding of `txn.FreezeAccount`. The + `txn.FreezeAsset` holding of `txn.Sender` is _not_ made + available. + * A Box is _available_ to an Approval Program if _any_ transaction in the same group contains a box reference (`txn.Boxes`) that denotes diff --git a/data/transactions/logic/TEAL_opcodes.md b/data/transactions/logic/TEAL_opcodes.md index 54a80db8f9..7752b1c182 100644 --- a/data/transactions/logic/TEAL_opcodes.md +++ b/data/transactions/logic/TEAL_opcodes.md @@ -838,7 +838,7 @@ Almost all smart contracts should use simpler and smaller methods (such as the [ - Availability: v2 - Mode: Application -params: Txn.Accounts offset (or, since v4, an _available_ account address), _available_ application id (or, since v4, a Txn.ForeignApps offset). Return: value. +params: Txn.Accounts offset (or, since v4, an _available_ account address). Return: value. ## app_opted_in @@ -1037,7 +1037,7 @@ params: Txn.ForeignApps offset or an _available_ app id. Return: did_exist flag - Availability: v3 - Mode: Application -params: Txn.Accounts offset (or, since v4, an _available_ account address), _available_ application id (or, since v4, a Txn.ForeignApps offset). Return: value. +params: Txn.Accounts offset (or, since v4, an _available_ account address). Return: value. ## pushbytes bytes diff --git a/data/transactions/logic/doc.go b/data/transactions/logic/doc.go index 31a0412fa3..c64dbff672 100644 --- a/data/transactions/logic/doc.go +++ b/data/transactions/logic/doc.go @@ -337,8 +337,8 @@ var opDocExtras = map[string]string{ "pushints": "pushints args are not added to the intcblock during assembly processes", "getbit": "see explanation of bit ordering in setbit", "setbit": "When A is a uint64, index 0 is the least significant bit. Setting bit 3 to 1 on the integer 0 yields 8, or 2^3. When A is a byte array, index 0 is the leftmost bit of the leftmost byte. Setting bits 0 through 11 to 1 in a 4-byte-array of 0s yields the byte array 0xfff00000. Setting bit 3 to 1 on the 1-byte-array 0x00 yields the byte array 0x10.", - "balance": "params: Txn.Accounts offset (or, since v4, an _available_ account address), _available_ application id (or, since v4, a Txn.ForeignApps offset). Return: value.", - "min_balance": "params: Txn.Accounts offset (or, since v4, an _available_ account address), _available_ application id (or, since v4, a Txn.ForeignApps offset). Return: value.", + "balance": "params: Txn.Accounts offset (or, since v4, an _available_ account address). Return: value.", + "min_balance": "params: Txn.Accounts offset (or, since v4, an _available_ account address). Return: value.", "app_opted_in": "params: Txn.Accounts offset (or, since v4, an _available_ account address), _available_ 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 _available_ account address), 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 _available_ account address), _available_ application id (or, since v4, a Txn.ForeignApps offset), state key. Return: did_exist flag (top of the stack, 1 if the application and key existed and 0 otherwise), value. The value is zero (of type uint64) if the key does not exist.", diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index aed48c1dd2..80069966d2 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -4108,12 +4108,7 @@ func opAppOptedIn(cx *EvalContext) error { last := len(cx.stack) - 1 // app prev := last - 1 // account - addr, _, err := cx.accountReference(cx.stack[prev]) - if err != nil { - return err - } - - app, err := cx.appReference(cx.stack[last].Uint, false) + addr, app, _, err := cx.localsReference(cx.stack[prev], cx.stack[last].Uint) if err != nil { return err } diff --git a/data/transactions/logic/langspec.json b/data/transactions/logic/langspec.json index cbfb4b0d95..a5c42c7b15 100644 --- a/data/transactions/logic/langspec.json +++ b/data/transactions/logic/langspec.json @@ -1462,7 +1462,7 @@ "Returns": "U", "Size": 1, "Doc": "balance for account A, in microalgos. The balance is observed after the effects of previous transactions in the group, and after the fee for the current transaction is deducted. Changes caused by inner transactions are observable immediately following `itxn_submit`", - "DocExtra": "params: Txn.Accounts offset (or, since v4, an _available_ account address), _available_ application id (or, since v4, a Txn.ForeignApps offset). Return: value.", + "DocExtra": "params: Txn.Accounts offset (or, since v4, an _available_ account address). Return: value.", "IntroducedVersion": 2, "Groups": [ "State Access" @@ -1689,7 +1689,7 @@ "Returns": "U", "Size": 1, "Doc": "minimum required balance for account A, in microalgos. Required balance is affected by ASA, App, and Box usage. When creating or opting into an app, the minimum balance grows before the app code runs, therefore the increase is visible there. When deleting or closing out, the minimum balance decreases after the app executes. Changes caused by inner transactions or box usage are observable immediately following the opcode effecting the change.", - "DocExtra": "params: Txn.Accounts offset (or, since v4, an _available_ account address), _available_ application id (or, since v4, a Txn.ForeignApps offset). Return: value.", + "DocExtra": "params: Txn.Accounts offset (or, since v4, an _available_ account address). Return: value.", "IntroducedVersion": 3, "Groups": [ "State Access" diff --git a/data/transactions/logic/resources_test.go b/data/transactions/logic/resources_test.go index df2b456810..32d2f4591b 100644 --- a/data/transactions/logic/resources_test.go +++ b/data/transactions/logic/resources_test.go @@ -85,6 +85,7 @@ pop; pop; int 1 _, _, ledger := logic.MakeSampleEnv() ledger.NewAccount(appl0.Sender, 100_000) + ledger.NewAccount(appl1.Sender, 100_000) ledger.NewApp(appl0.Sender, 500, basics.AppParams{}) ledger.NewLocals(appl0.Sender, 500) // opt in // Now txn0 passes, but txn1 has an error because it can't see app 500 @@ -96,6 +97,27 @@ pop; pop; int 1 // is the one from tx0. logic.TestApps(t, sources, txntest.Group(&appl0, &appl2), 9, ledger) + // Checking if an account is opted in has pretty much the same rules + optInCheck := ` +int 0 // Sender +int 500 +app_opted_in +` + + sources = []string{optInCheck, optInCheck} + // app_opted_in requires the address and the app exist, else the program fails + logic.TestApps(t, sources, txntest.Group(&appl0, &appl1), 8, nil, + logic.NewExpect(0, "no account")) + + // Now txn0 passes, but txn1 has an error because it can't see app 500 + logic.TestApps(t, sources, txntest.Group(&appl0, &appl1), 9, ledger, + logic.NewExpect(1, "invalid Local State access")) + + // But it's ok in appl2, because appl2 uses the same Sender, even though the + // foreign-app is not repeated in appl2 because the holding being accessed + // is the one from tx0. + logic.TestApps(t, sources, txntest.Group(&appl0, &appl2), 9, ledger) + // Now, confirm that *setting* a local state in tx1 that was made available // in tx0 works. The extra check here is that the change is recorded // properly in EvalDelta. @@ -706,6 +728,14 @@ func TestAccessMyLocals(t *testing.T) { app_local_get int 7 == +` + logic.TestApp(t, source, ep) + + // They can also see that they are opted in, though it's a weird question to ask. + source = ` + int 0 + int 0 + app_opted_in ` logic.TestApp(t, source, ep) })