diff --git a/cmd/opdoc/opdoc.go b/cmd/opdoc/opdoc.go index 6ceaf851bc..334942a324 100644 --- a/cmd/opdoc/opdoc.go +++ b/cmd/opdoc/opdoc.go @@ -105,6 +105,11 @@ func assetParamsFieldsMarkdown(out io.Writer) { 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) +} + func immediateMarkdown(op *logic.OpSpec) string { markdown := "" for _, imm := range op.Details.Immediates { @@ -179,6 +184,8 @@ func opToMarkdown(out io.Writer, op *logic.OpSpec) (err error) { assetHoldingFieldsMarkdown(out) } else if op.Name == "asset_params_get" { assetParamsFieldsMarkdown(out) + } else if op.Name == "app_params_get" { + appParamsFieldsMarkdown(out) } ode := logic.OpDocExtra(op.Name) if ode != "" { @@ -240,6 +247,9 @@ func argEnum(name string) []string { if name == "asset_params_get" { return logic.AssetParamsFieldNames } + if name == "app_params_get" { + return logic.AppParamsFieldNames + } return nil } @@ -281,6 +291,9 @@ func argEnumTypes(name string) string { if name == "asset_params_get" { return typeString(logic.AssetParamsFieldTypes) } + if name == "app_params_get" { + return typeString(logic.AppParamsFieldTypes) + } return "" } @@ -344,6 +357,10 @@ func main() { 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) + appparams.Close() + langspecjs, _ := os.Create("langspec.json") enc := json.NewEncoder(langspecjs) enc.Encode(buildLanguageSpec(opGroups)) diff --git a/config/consensus.go b/config/consensus.go index 654ae09a7c..5bee45ac65 100644 --- a/config/consensus.go +++ b/config/consensus.go @@ -942,7 +942,7 @@ func initConsensusProtocols() { v28 := v27 v28.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{} - // Enable TEAL 4 + // Enable TEAL 4 / AVM 0.9 v28.LogicSigVersion = 4 // Enable support for larger app program size v28.MaxExtraAppProgramPages = 3 @@ -984,6 +984,9 @@ func initConsensusProtocols() { vFuture.CompactCertWeightThreshold = (1 << 32) * 30 / 100 vFuture.CompactCertSecKQ = 128 + // Enable TEAL 5 / AVM 1.0 + vFuture.LogicSigVersion = 5 + Consensus[protocol.ConsensusFuture] = vFuture } diff --git a/data/transactions/logic/README.md b/data/transactions/logic/README.md index bcdae883aa..0958ad4b0d 100644 --- a/data/transactions/logic/README.md +++ b/data/transactions/logic/README.md @@ -233,17 +233,17 @@ Some of these have immediate data in the byte or bytes after the opcode. | 2 | FirstValid | uint64 | round number | | 3 | FirstValidTime | uint64 | Causes program to fail; reserved for future use | | 4 | LastValid | uint64 | round number | -| 5 | Note | []byte | | -| 6 | Lease | []byte | | +| 5 | Note | []byte | Any data up to 1024 bytes | +| 6 | Lease | []byte | 32 byte lease value | | 7 | Receiver | []byte | 32 byte address | | 8 | Amount | uint64 | micro-Algos | | 9 | CloseRemainderTo | []byte | 32 byte address | | 10 | VotePK | []byte | 32 byte address | | 11 | SelectionPK | []byte | 32 byte address | -| 12 | VoteFirst | uint64 | | -| 13 | VoteLast | uint64 | | -| 14 | VoteKeyDilution | uint64 | | -| 15 | Type | []byte | | +| 12 | VoteFirst | uint64 | The first round that the participation key is valid. | +| 13 | VoteLast | uint64 | The last round that the participation key is valid. | +| 14 | VoteKeyDilution | uint64 | Dilution for the 2-level participation key | +| 15 | Type | []byte | Transaction type as bytes | | 16 | TypeEnum | uint64 | See table below | | 17 | XferAsset | uint64 | Asset ID | | 18 | AssetAmount | uint64 | value in Asset's units | @@ -309,7 +309,7 @@ Global fields are fields that are common to all the transactions in the group. I **Asset Fields** -Asset fields include `AssetHolding` and `AssetParam` fields that are used in `asset_read_*` opcodes +Asset fields include `AssetHolding` and `AssetParam` fields that are used in the `asset_holding_get` and `asset_params_get` opcodes. | Index | Name | Type | Notes | | --- | --- | --- | --- | @@ -330,6 +330,23 @@ Asset fields include `AssetHolding` and `AssetParam` fields that are used in `as | 8 | AssetReserve | []byte | Reserve address | | 9 | AssetFreeze | []byte | Freeze address | | 10 | AssetClawback | []byte | Clawback address | +| 11 | AssetCreator | []byte | Creator address | + + +**App Fields** + +App fields used in the `app_params_get` opcode. + +| Index | Name | Type | Notes | +| --- | --- | --- | --- | +| 0 | AppApprovalProgram | []byte | Bytecode of Approval Program | +| 1 | AppClearStateProgram | []byte | Bytecode of Clear State Program | +| 2 | AppGlobalNumUint | uint64 | Number of uint64 values allowed in Global State | +| 3 | AppGlobalNumByteSlice | uint64 | Number of byte array values allowed in Global State | +| 4 | AppLocalNumUint | uint64 | Number of uint64 values allowed in Local State | +| 5 | AppLocalNumByteSlice | uint64 | Number of byte array values allowed in Local State | +| 6 | AppExtraProgramPages | uint64 | Number of Extra Program Pages of code space | +| 7 | AppCreator | []byte | Creator address | ### Flow Control @@ -368,6 +385,7 @@ Asset fields include `AssetHolding` and `AssetParam` fields that are used in `as | `app_global_del` | delete key A from a global state of the current application | | `asset_holding_get i` | read from account A and asset B holding field X (imm arg) => {0 or 1 (top), value} | | `asset_params_get i` | read from asset A params field X (imm arg) => {0 or 1 (top), value} | +| `app_params_get i` | read from app A params field X (imm arg) => {0 or 1 (top), value} | # Assembler Syntax diff --git a/data/transactions/logic/README_in.md b/data/transactions/logic/README_in.md index 2337960570..8f62938f45 100644 --- a/data/transactions/logic/README_in.md +++ b/data/transactions/logic/README_in.md @@ -122,12 +122,18 @@ Global fields are fields that are common to all the transactions in the group. I **Asset Fields** -Asset fields include `AssetHolding` and `AssetParam` fields that are used in `asset_read_*` opcodes +Asset fields include `AssetHolding` and `AssetParam` fields that are used in the `asset_holding_get` and `asset_params_get` opcodes. @@ asset_holding_fields.md @@ @@ asset_params_fields.md @@ +**App Fields** + +App fields used in the `app_params_get` opcode. + +@@ app_params_fields.md @@ + ### Flow Control @@ Flow_Control.md @@ diff --git a/data/transactions/logic/TEAL_opcodes.md b/data/transactions/logic/TEAL_opcodes.md index 08905d5dca..e52d10239b 100644 --- a/data/transactions/logic/TEAL_opcodes.md +++ b/data/transactions/logic/TEAL_opcodes.md @@ -18,7 +18,7 @@ Ops have a 'cost' of 1 unless otherwise specified. - SHA256 hash of value X, yields [32]byte - **Cost**: - 7 (LogicSigVersion = 1) - - 35 (2 <= LogicSigVersion <= 4) + - 35 (2 <= LogicSigVersion <= 5) ## keccak256 @@ -28,7 +28,7 @@ Ops have a 'cost' of 1 unless otherwise specified. - Keccak256 hash of value X, yields [32]byte - **Cost**: - 26 (LogicSigVersion = 1) - - 130 (2 <= LogicSigVersion <= 4) + - 130 (2 <= LogicSigVersion <= 5) ## sha512_256 @@ -38,7 +38,7 @@ Ops have a 'cost' of 1 unless otherwise specified. - SHA512_256 hash of value X, yields [32]byte - **Cost**: - 9 (LogicSigVersion = 1) - - 45 (2 <= LogicSigVersion <= 4) + - 45 (2 <= LogicSigVersion <= 5) ## ed25519verify @@ -374,17 +374,17 @@ Overflow is an error condition which halts execution and fails the transaction. | 2 | FirstValid | uint64 | round number | | 3 | FirstValidTime | uint64 | Causes program to fail; reserved for future use | | 4 | LastValid | uint64 | round number | -| 5 | Note | []byte | | -| 6 | Lease | []byte | | +| 5 | Note | []byte | Any data up to 1024 bytes | +| 6 | Lease | []byte | 32 byte lease value | | 7 | Receiver | []byte | 32 byte address | | 8 | Amount | uint64 | micro-Algos | | 9 | CloseRemainderTo | []byte | 32 byte address | | 10 | VotePK | []byte | 32 byte address | | 11 | SelectionPK | []byte | 32 byte address | -| 12 | VoteFirst | uint64 | | -| 13 | VoteLast | uint64 | | -| 14 | VoteKeyDilution | uint64 | | -| 15 | Type | []byte | | +| 12 | VoteFirst | uint64 | The first round that the participation key is valid. | +| 13 | VoteLast | uint64 | The last round that the participation key is valid. | +| 14 | VoteKeyDilution | uint64 | Dilution for the 2-level participation key | +| 15 | Type | []byte | Transaction type as bytes | | 16 | TypeEnum | uint64 | See table below | | 17 | XferAsset | uint64 | Asset ID | | 18 | AssetAmount | uint64 | value in Asset's units | @@ -877,10 +877,36 @@ 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 | 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 i + +- Opcode: 0x72 {uint8 app params field index} +- Pops: *... stack*, uint64 +- Pushes: *... stack*, any, uint64 +- read from app A params field X (imm arg) => {0 or 1 (top), value} +- LogicSigVersion >= 5 +- Mode: Application + +`app_params_get` Fields: + +| Index | Name | Type | Notes | +| --- | --- | --- | --- | +| 0 | AppApprovalProgram | []byte | Bytecode of Approval Program | +| 1 | AppClearStateProgram | []byte | Bytecode of Clear State Program | +| 2 | AppGlobalNumUint | uint64 | Number of uint64 values allowed in Global State | +| 3 | AppGlobalNumByteSlice | uint64 | Number of byte array values allowed in Global State | +| 4 | AppLocalNumUint | uint64 | Number of uint64 values allowed in Local State | +| 5 | AppLocalNumByteSlice | uint64 | Number of byte array values allowed in Local State | +| 6 | AppExtraProgramPages | uint64 | Number of Extra Program Pages of code space | +| 7 | AppCreator | []byte | Creator address | + + +params: Txn.ForeignApps offset or an app id that appears in Txn.ForeignApps. Return: did_exist flag (1 if exist and 0 otherwise), value. + ## min_balance - Opcode: 0x78 diff --git a/data/transactions/logic/assembler.go b/data/transactions/logic/assembler.go index bf09adb068..8b4c3e2227 100644 --- a/data/transactions/logic/assembler.go +++ b/data/transactions/logic/assembler.go @@ -980,11 +980,11 @@ func assembleGtxnsa(ops *OpStream, spec *OpSpec, args []string) error { func assembleGlobal(ops *OpStream, spec *OpSpec, args []string) error { if len(args) != 1 { - return ops.error("global expects one argument") + return ops.errorf("%s expects one argument", spec.Name) } fs, ok := globalFieldSpecByName[args[0]] if !ok { - return ops.errorf("global unknown field: %#v", args[0]) + return ops.errorf("%s unknown field: %#v", spec.Name, args[0]) } if fs.version > ops.Version { // no return here. we may as well continue to maintain typestack @@ -1001,11 +1001,11 @@ func assembleGlobal(ops *OpStream, spec *OpSpec, args []string) error { func assembleAssetHolding(ops *OpStream, spec *OpSpec, args []string) error { if len(args) != 1 { - return ops.error("asset_holding_get expects one argument") + return ops.errorf("%s expects one argument", spec.Name) } val, ok := assetHoldingFields[args[0]] if !ok { - return ops.errorf("asset_holding_get unknown arg: %#v", args[0]) + return ops.errorf("%s unknown arg: %#v", spec.Name, args[0]) } ops.pending.WriteByte(spec.Opcode) ops.pending.WriteByte(uint8(val)) @@ -1015,11 +1015,11 @@ func assembleAssetHolding(ops *OpStream, spec *OpSpec, args []string) error { func assembleAssetParams(ops *OpStream, spec *OpSpec, args []string) error { if len(args) != 1 { - return ops.error("asset_params_get expects one argument") + return ops.errorf("%s expects one argument", spec.Name) } val, ok := assetParamsFields[args[0]] if !ok { - return ops.errorf("asset_params_get unknown arg: %#v", args[0]) + return ops.errorf("%s unknown arg: %#v", spec.Name, args[0]) } ops.pending.WriteByte(spec.Opcode) ops.pending.WriteByte(uint8(val)) @@ -1027,6 +1027,20 @@ func assembleAssetParams(ops *OpStream, spec *OpSpec, args []string) error { return nil } +func assembleAppParams(ops *OpStream, spec *OpSpec, args []string) error { + if len(args) != 1 { + return ops.errorf("%s expects one argument", spec.Name) + } + val, ok := appParamsFields[args[0]] + if !ok { + return ops.errorf("%s unknown arg: %#v", spec.Name, args[0]) + } + ops.pending.WriteByte(spec.Opcode) + ops.pending.WriteByte(uint8(val)) + ops.returns(AppParamsFieldTypes[val], StackUint64) + return nil +} + type assembleFunc func(*OpStream, *OpSpec, []string) error // Basic assembly. Any extra bytes of opcode are encoded as byte immediates. @@ -2160,7 +2174,7 @@ func disGlobal(dis *disassembleState, spec *OpSpec) (string, error) { if int(garg) >= len(GlobalFieldNames) { return "", fmt.Errorf("invalid global arg index %d at pc=%d", garg, dis.pc) } - return fmt.Sprintf("global %s", GlobalFieldNames[garg]), nil + return fmt.Sprintf("%s %s", spec.Name, GlobalFieldNames[garg]), nil } func disBranch(dis *disassembleState, spec *OpSpec) (string, error) { @@ -2202,7 +2216,7 @@ func disAssetHolding(dis *disassembleState, spec *OpSpec) (string, error) { if int(arg) >= len(AssetHoldingFieldNames) { return "", fmt.Errorf("invalid asset holding arg index %d at pc=%d", arg, dis.pc) } - return fmt.Sprintf("asset_holding_get %s", AssetHoldingFieldNames[arg]), nil + return fmt.Sprintf("%s %s", spec.Name, AssetHoldingFieldNames[arg]), nil } func disAssetParams(dis *disassembleState, spec *OpSpec) (string, error) { @@ -2216,7 +2230,21 @@ func disAssetParams(dis *disassembleState, spec *OpSpec) (string, error) { if int(arg) >= len(AssetParamsFieldNames) { return "", fmt.Errorf("invalid asset params arg index %d at pc=%d", arg, dis.pc) } - return fmt.Sprintf("asset_params_get %s", AssetParamsFieldNames[arg]), nil + return fmt.Sprintf("%s %s", spec.Name, AssetParamsFieldNames[arg]), nil +} + +func disAppParams(dis *disassembleState, spec *OpSpec) (string, error) { + lastIdx := dis.pc + 1 + if len(dis.program) <= lastIdx { + missing := lastIdx - len(dis.program) + 1 + return "", fmt.Errorf("unexpected %s opcode end: missing %d bytes", spec.Name, missing) + } + dis.nextpc = dis.pc + 2 + arg := dis.program[dis.pc+1] + if int(arg) >= len(AppParamsFieldNames) { + return "", fmt.Errorf("invalid app params arg index %d at pc=%d", arg, dis.pc) + } + return fmt.Sprintf("%s %s", spec.Name, AppParamsFieldNames[arg]), nil } type disInfo struct { diff --git a/data/transactions/logic/assembler_test.go b/data/transactions/logic/assembler_test.go index 26eaab9271..51c4ec0210 100644 --- a/data/transactions/logic/assembler_test.go +++ b/data/transactions/logic/assembler_test.go @@ -289,6 +289,11 @@ gload 0 0 gloads 0 gaid 0 gaids +int 100 +` + +const v5Nonsense = ` +app_params_get AppExtraProgramPages ` var nonsense = map[uint64]string{ @@ -296,13 +301,15 @@ var nonsense = map[uint64]string{ 2: v1Nonsense + v2Nonsense, 3: v1Nonsense + v2Nonsense + v3Nonsense, 4: v1Nonsense + v2Nonsense + v3Nonsense + v4Nonsense, + 5: v1Nonsense + v2Nonsense + v3Nonsense + v4Nonsense + v5Nonsense, } var compiled = map[uint64]string{ 1: "012008b7a60cf8acd19181cf959a12f8acd19181cf951af8acd19181cf15f8acd191810f01020026050212340c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d024242047465737400320032013202320328292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e0102222324252104082209240a220b230c240d250e230f23102311231223132314181b1c2b1716154000032903494", 2: "022008b7a60cf8acd19181cf959a12f8acd19181cf951af8acd19181cf15f8acd191810f01020026050212340c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d024242047465737400320032013202320328292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e0102222324252104082209240a220b230c240d250e230f23102311231223132314181b1c2b171615400003290349483403350222231d4a484848482a50512a63222352410003420000432105602105612105270463484821052b62482b642b65484821052b2106662b21056721072b682b692107210570004848210771004848361c0037001a0031183119311b311d311e311f3120210721051e312131223123312431253126312731283129312a312b312c312d312e312f", 3: "032008b7a60cf8acd19181cf959a12f8acd19181cf951af8acd19181cf15f8acd191810f01020026050212340c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d024242047465737400320032013202320328292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e0102222324252104082209240a220b230c240d250e230f23102311231223132314181b1c2b171615400003290349483403350222231d4a484848482a50512a63222352410003420000432105602105612105270463484821052b62482b642b65484821052b2106662b21056721072b682b692107210570004848210771004848361c0037001a0031183119311b311d311e311f3120210721051e312131223123312431253126312731283129312a312b312c312d312e312f4478222105531421055427042106552105082106564c4d4b02210538212106391c0081e80780046a6f686e", - 4: "042004010200b7a60c26040242420c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f23102311231223132314181b1c28171615400003290349483403350222231d4a484848482a50512a632223524100034200004322602261222b634848222862482864286548482228236628226724286828692422700048482471004848361c0037001a0031183119311b311d311e311f312024221e312131223123312431253126312731283129312a312b312c312d312e312f44782522531422542b2355220823564c4d4b0222382123391c0081e80780046a6f686e2281d00f24231f880003420001892223902291922394239593a0a1a2a3a4a5a6a7a8a9aaabacadae23af3a00003b003c003d", + 4: "042004010200b7a60c26040242420c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f23102311231223132314181b1c28171615400003290349483403350222231d4a484848482a50512a632223524100034200004322602261222b634848222862482864286548482228236628226724286828692422700048482471004848361c0037001a0031183119311b311d311e311f312024221e312131223123312431253126312731283129312a312b312c312d312e312f44782522531422542b2355220823564c4d4b0222382123391c0081e80780046a6f686e2281d00f24231f880003420001892223902291922394239593a0a1a2a3a4a5a6a7a8a9aaabacadae23af3a00003b003c003d8164", + 5: "052004010200b7a60c26040242420c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f23102311231223132314181b1c28171615400003290349483403350222231d4a484848482a50512a632223524100034200004322602261222b634848222862482864286548482228236628226724286828692422700048482471004848361c0037001a0031183119311b311d311e311f312024221e312131223123312431253126312731283129312a312b312c312d312e312f44782522531422542b2355220823564c4d4b0222382123391c0081e80780046a6f686e2281d00f24231f880003420001892223902291922394239593a0a1a2a3a4a5a6a7a8a9aaabacadae23af3a00003b003c003d81647206", } func pseudoOp(opcode string) bool { diff --git a/data/transactions/logic/doc.go b/data/transactions/logic/doc.go index 4a4520c5d7..30e9327a14 100644 --- a/data/transactions/logic/doc.go +++ b/data/transactions/logic/doc.go @@ -122,6 +122,7 @@ var opDocByName = map[string]string{ "app_global_del": "delete key A from a global state of the current application", "asset_holding_get": "read from account A and asset B holding field X (imm arg) => {0 or 1 (top), value}", "asset_params_get": "read from asset A params field X (imm arg) => {0 or 1 (top), value}", + "app_params_get": "read from app A params field X (imm arg) => {0 or 1 (top), value}", "assert": "immediately fail unless value X is a non-zero number", "callsub": "branch unconditionally to TARGET, saving the next instruction on the call stack", "retsub": "pop the top instruction from the call stack and branch to it", @@ -176,6 +177,7 @@ var opcodeImmediateNotes = map[string]string{ "dig": "{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}", } // OpImmediateNote returns a short string about immediate data which follows the op byte @@ -222,6 +224,7 @@ var opDocExtras = map[string]string{ "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.", } // OpDocExtra returns extra documentation text about an op @@ -236,7 +239,7 @@ var OpGroups = map[string][]string{ "Byteslice 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", "gtxna", "gtxns", "gtxnsa", "global", "load", "store", "gload", "gloads", "gaid", "gaids"}, "Flow Control": {"err", "bnz", "bz", "b", "return", "pop", "dup", "dup2", "dig", "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"}, + "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"}, } // OpCost indicates the cost of an operation over the range of @@ -304,18 +307,22 @@ 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", - "Receiver": "32 byte address", - "Amount": "micro-Algos", - "CloseRemainderTo": "32 byte address", - "VotePK": "32 byte address", - "SelectionPK": "32 byte address", - //"VoteFirst": "", - //"VoteLast": "", + "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", + "Type": "Transaction type as bytes", "TypeEnum": "See table below", "XferAsset": "Asset ID", "AssetAmount": "value in Asset's units", @@ -424,4 +431,17 @@ var AssetParamsFieldDocs = map[string]string{ "AssetReserve": "Reserve address", "AssetFreeze": "Freeze address", "AssetClawback": "Clawback address", + "AssetCreator": "Creator address", +} + +// 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", + "AppGlobalNumByteSlice": "Number of byte array values allowed in Global State", + "AppLocalNumUint": "Number of uint64 values allowed in Local State", + "AppLocalNumByteSlice": "Number of byte array values allowed in Local State", + "AppExtraProgramPages": "Number of Extra Program Pages of code space", + "AppCreator": "Creator address", } diff --git a/data/transactions/logic/doc_test.go b/data/transactions/logic/doc_test.go index 3ecb0dfd6f..6257839937 100644 --- a/data/transactions/logic/doc_test.go +++ b/data/transactions/logic/doc_test.go @@ -40,6 +40,14 @@ func TestOpDocs(t *testing.T) { t.Errorf("error: doc for op %#v missing from opDocByName", op) } } + + require.Len(t, txnFieldDocs, len(TxnFieldNames)) + require.Len(t, onCompletionDescriptions, len(OnCompletionNames)) + require.Len(t, globalFieldDocs, len(GlobalFieldNames)) + require.Len(t, AssetHoldingFieldDocs, len(AssetHoldingFieldNames)) + require.Len(t, AssetParamsFieldDocs, len(AssetParamsFieldNames)) + require.Len(t, AppParamsFieldDocs, len(AppParamsFieldNames)) + require.Len(t, TypeNameDescriptions, len(TxnTypeNames)) } func TestOpGroupCoverage(t *testing.T) { diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 21ba351623..b04c3948e5 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -143,7 +143,8 @@ type LedgerForLogic interface { LatestTimestamp() int64 AssetHolding(addr basics.Address, assetIdx basics.AssetIndex) (basics.AssetHolding, error) - AssetParams(aidx basics.AssetIndex) (basics.AssetParams, error) + AssetParams(aidx basics.AssetIndex) (basics.AssetParams, basics.Address, error) + AppParams(aidx basics.AppIndex) (basics.AppParams, basics.Address, error) ApplicationID() basics.AppIndex CreatorAddress() basics.Address OptedIn(addr basics.Address, appIdx basics.AppIndex) (bool, error) @@ -782,7 +783,7 @@ func opAssert(cx *evalContext) { cx.stack = cx.stack[:last] return } - cx.err = errors.New("assert failed") + cx.err = fmt.Errorf("assert failed pc=%d", cx.pc) } func opSwap(cx *evalContext) { @@ -1718,7 +1719,7 @@ func (cx *evalContext) assetHoldingEnumToValue(holding *basics.AssetHolding, fie return } -func (cx *evalContext) assetParamsEnumToValue(params *basics.AssetParams, field uint64) (sv stackValue, err error) { +func (cx *evalContext) assetParamsEnumToValue(params *basics.AssetParams, creator basics.Address, field uint64) (sv stackValue, err error) { switch AssetParamsField(field) { case AssetTotal: sv.Uint = params.Total @@ -1742,6 +1743,8 @@ func (cx *evalContext) assetParamsEnumToValue(params *basics.AssetParams, field sv.Bytes = params.Freeze[:] case AssetClawback: sv.Bytes = params.Clawback[:] + case AssetCreator: + sv.Bytes = creator[:] default: err = fmt.Errorf("invalid asset params field %d", field) return @@ -1755,6 +1758,37 @@ func (cx *evalContext) assetParamsEnumToValue(params *basics.AssetParams, field return } +func (cx *evalContext) appParamsEnumToValue(params *basics.AppParams, creator basics.Address, field uint64) (sv stackValue, err error) { + switch AppParamsField(field) { + case AppApprovalProgram: + sv.Bytes = params.ApprovalProgram[:] + case AppClearStateProgram: + sv.Bytes = params.ClearStateProgram[:] + case AppGlobalNumUint: + sv.Uint = params.GlobalStateSchema.NumUint + case AppGlobalNumByteSlice: + sv.Uint = params.GlobalStateSchema.NumByteSlice + case AppLocalNumUint: + sv.Uint = params.LocalStateSchema.NumUint + case AppLocalNumByteSlice: + sv.Uint = params.LocalStateSchema.NumByteSlice + case AppExtraProgramPages: + sv.Uint = uint64(params.ExtraProgramPages) + case AppCreator: + sv.Bytes = creator[:] + default: + err = fmt.Errorf("invalid app params field %d", field) + return + } + + appParamsField := AppParamsField(field) + appParamsFieldType := AppParamsFieldTypes[appParamsField] + if !typecheck(appParamsFieldType, sv.argType()) { + err = fmt.Errorf("%s expected field type is %s but got %s", appParamsField.String(), appParamsFieldType.String(), sv.argType().String()) + } + return +} + // TxnFieldToTealValue is a thin wrapper for txnFieldToStack for external use func TxnFieldToTealValue(txn *transactions.Transaction, groupIndex int, field TxnField, arrayFieldIdx uint64) (basics.TealValue, error) { cx := evalContext{EvalParams: EvalParams{GroupIndex: groupIndex}} @@ -2974,10 +3008,42 @@ func opAssetParamsGet(cx *evalContext) { var exist uint64 = 0 var value stackValue - if params, err := cx.Ledger.AssetParams(asset); err == nil { + if params, creator, err := cx.Ledger.AssetParams(asset); err == nil { + // params exist, read the value + exist = 1 + value, err = cx.assetParamsEnumToValue(¶ms, creator, paramIdx) + if err != nil { + cx.err = err + return + } + } + + cx.stack[last] = value + cx.stack = append(cx.stack, stackValue{Uint: exist}) +} + +func opAppParamsGet(cx *evalContext) { + last := len(cx.stack) - 1 // app + + if cx.Ledger == nil { + cx.err = fmt.Errorf("ledger not available") + return + } + + paramIdx := uint64(cx.program[cx.pc+1]) + + app, err := appReference(cx, cx.stack[last].Uint, true) + if err != nil { + cx.err = err + return + } + + var exist uint64 = 0 + var value stackValue + if params, creator, err := cx.Ledger.AppParams(app); err == nil { // params exist, read the value exist = 1 - value, err = cx.assetParamsEnumToValue(¶ms, paramIdx) + value, err = cx.appParamsEnumToValue(¶ms, creator, paramIdx) if err != nil { cx.err = err return diff --git a/data/transactions/logic/evalStateful_test.go b/data/transactions/logic/evalStateful_test.go index ad5dce28b1..884b7e48a2 100644 --- a/data/transactions/logic/evalStateful_test.go +++ b/data/transactions/logic/evalStateful_test.go @@ -35,31 +35,42 @@ type balanceRecord struct { addr basics.Address balance uint64 locals map[basics.AppIndex]basics.TealKeyValue - holdings map[uint64]basics.AssetHolding + holdings map[basics.AssetIndex]basics.AssetHolding mods map[basics.AppIndex]map[string]basics.ValueDelta } -// In our test ledger, we don't store the AppParams with its creator, -// so we need to carry the creator around with the params, +// In our test ledger, we don't store the creatables with their +// creators, so we need to carry the creator around with them. type appParams struct { basics.AppParams Creator basics.Address } +type asaParams struct { + basics.AssetParams + Creator basics.Address +} + type testLedger struct { balances map[basics.Address]balanceRecord applications map[basics.AppIndex]appParams - assets map[basics.AssetIndex]basics.AssetParams + assets map[basics.AssetIndex]asaParams trackedCreatables map[int]basics.CreatableIndex appID basics.AppIndex creatorAddr basics.Address mods map[basics.AppIndex]map[string]basics.ValueDelta } -func makeSchemas(li uint64, lb uint64, gi uint64, gb uint64) basics.StateSchemas { - return basics.StateSchemas{ - LocalStateSchema: basics.StateSchema{NumUint: li, NumByteSlice: lb}, - GlobalStateSchema: basics.StateSchema{NumUint: gi, NumByteSlice: gb}, +func makeApp(li uint64, lb uint64, gi uint64, gb uint64) basics.AppParams { + return basics.AppParams{ + ApprovalProgram: []byte{}, + ClearStateProgram: []byte{}, + GlobalState: map[string]basics.TealValue{}, + StateSchemas: basics.StateSchemas{ + LocalStateSchema: basics.StateSchema{NumUint: li, NumByteSlice: lb}, + GlobalStateSchema: basics.StateSchema{NumUint: gi, NumByteSlice: gb}, + }, + ExtraProgramPages: 0, } } @@ -68,20 +79,29 @@ func makeBalanceRecord(addr basics.Address, balance uint64) balanceRecord { addr: addr, balance: balance, locals: make(map[basics.AppIndex]basics.TealKeyValue), - holdings: make(map[uint64]basics.AssetHolding), + holdings: make(map[basics.AssetIndex]basics.AssetHolding), mods: make(map[basics.AppIndex]map[string]basics.ValueDelta), } return br } +func makeSampleEnv() (EvalParams, *testLedger) { + txn := makeSampleTxn() + ep := defaultEvalParams(nil, &txn) + ep.TxnGroup = makeSampleTxnGroup(txn) + ledger := makeTestLedger(map[basics.Address]uint64{}) + ep.Ledger = ledger + return ep, ledger +} + func makeTestLedger(balances map[basics.Address]uint64) *testLedger { l := new(testLedger) l.balances = make(map[basics.Address]balanceRecord) for addr, balance := range balances { - l.balances[addr] = makeBalanceRecord(addr, balance) + l.newAccount(addr, balance) } l.applications = make(map[basics.AppIndex]appParams) - l.assets = make(map[basics.AssetIndex]basics.AssetParams) + l.assets = make(map[basics.AssetIndex]asaParams) l.trackedCreatables = make(map[int]basics.CreatableIndex) l.mods = make(map[basics.AppIndex]map[string]basics.ValueDelta) return l @@ -95,28 +115,39 @@ func (l *testLedger) reset() { } } -func (l *testLedger) newApp(addr basics.Address, appID basics.AppIndex, schemas basics.StateSchemas) { +func (l *testLedger) newAccount(addr basics.Address, balance uint64) { + l.balances[addr] = makeBalanceRecord(addr, balance) +} + +func (l *testLedger) newApp(creator basics.Address, appID basics.AppIndex, params basics.AppParams) { l.appID = appID - appIdx := appID - l.applications[appIdx] = appParams{ - Creator: addr, - AppParams: basics.AppParams{ - StateSchemas: schemas, - GlobalState: make(basics.TealKeyValue), - }, + params = params.Clone() + if params.GlobalState == nil { + params.GlobalState = make(basics.TealKeyValue) } - br, ok := l.balances[addr] + l.applications[appID] = appParams{ + Creator: creator, + AppParams: params.Clone(), + } + br, ok := l.balances[creator] if !ok { - br = makeBalanceRecord(addr, 0) + br = makeBalanceRecord(creator, 0) } - br.locals[appIdx] = make(map[string]basics.TealValue) - l.balances[addr] = br + br.locals[appID] = make(map[string]basics.TealValue) + l.balances[creator] = br } -func (l *testLedger) newAsset(creator basics.Address, assetID uint64, params basics.AssetParams) { - l.assets[basics.AssetIndex(assetID)] = params - // We're not simulating details of ReserveAddress yet. - l.setHolding(creator, assetID, params.Total, params.DefaultFrozen) +func (l *testLedger) newAsset(creator basics.Address, assetID basics.AssetIndex, params basics.AssetParams) { + l.assets[assetID] = asaParams{ + Creator: creator, + AssetParams: params, + } + br, ok := l.balances[creator] + if !ok { + br = makeBalanceRecord(creator, 0) + } + br.holdings[assetID] = basics.AssetHolding{Amount: params.Total, Frozen: params.DefaultFrozen} + l.balances[creator] = br } func (l *testLedger) setHolding(addr basics.Address, assetID uint64, amount uint64, frozen bool) { @@ -124,7 +155,7 @@ func (l *testLedger) setHolding(addr basics.Address, assetID uint64, amount uint if !ok { br = makeBalanceRecord(addr, 0) } - br.holdings[assetID] = basics.AssetHolding{Amount: amount, Frozen: frozen} + br.holdings[basics.AssetIndex(assetID)] = basics.AssetHolding{Amount: amount, Frozen: frozen} l.balances[addr] = br } @@ -369,7 +400,7 @@ func (l *testLedger) GetCreatableID(groupIdx int) basics.CreatableIndex { func (l *testLedger) AssetHolding(addr basics.Address, assetID basics.AssetIndex) (basics.AssetHolding, error) { if br, ok := l.balances[addr]; ok { - if asset, ok := br.holdings[uint64(assetID)]; ok { + if asset, ok := br.holdings[assetID]; ok { return asset, nil } return basics.AssetHolding{}, fmt.Errorf("No asset for account") @@ -377,11 +408,18 @@ func (l *testLedger) AssetHolding(addr basics.Address, assetID basics.AssetIndex return basics.AssetHolding{}, fmt.Errorf("no such address") } -func (l *testLedger) AssetParams(assetID basics.AssetIndex) (basics.AssetParams, error) { +func (l *testLedger) AssetParams(assetID basics.AssetIndex) (basics.AssetParams, basics.Address, error) { if asset, ok := l.assets[assetID]; ok { - return asset, nil + return asset.AssetParams, asset.Creator, nil } - return basics.AssetParams{}, fmt.Errorf("no such asset") + return basics.AssetParams{}, basics.Address{}, fmt.Errorf("no such asset") +} + +func (l *testLedger) AppParams(appID basics.AppIndex) (basics.AppParams, basics.Address, error) { + if app, ok := l.applications[appID]; ok { + return app.AppParams, app.Creator, nil + } + return basics.AppParams{}, basics.Address{}, fmt.Errorf("no such app") } func (l *testLedger) ApplicationID() basics.AppIndex { @@ -596,7 +634,7 @@ pop txn.Txn.Sender: 1, }, ) - ledger.newApp(txn.Txn.Sender, 100, makeSchemas(0, 0, 0, 0)) + ledger.newApp(txn.Txn.Sender, 100, basics.AppParams{}) ledger.balances[txn.Txn.Sender].locals[100]["ALGO"] = algoValue ledger.newAsset(txn.Txn.Sender, 5, params) @@ -647,10 +685,9 @@ pop "arg_3", } for _, source := range disallowed { - ops, err := AssembleStringWithVersion(source, AssemblerMaxVersion) - require.NoError(t, err) + ops := testProg(t, source, AssemblerMaxVersion) ep := defaultEvalParams(nil, nil) - err = CheckStateful(ops.Program, ep) + err := CheckStateful(ops.Program, ep) require.Error(t, err) _, err = EvalStateful(ops.Program, ep) require.Error(t, err) @@ -671,6 +708,7 @@ pop "byte 0x01\napp_global_del", "int 0\nint 0\nasset_holding_get AssetFrozen", "int 0\nint 0\nasset_params_get AssetManager", + "int 0\nint 0\napp_params_get AppApprovalProgram", } for _, source := range statefulOpcodeCalls { @@ -689,67 +727,31 @@ pop require.True(t, modeAny.Any()) } -func testStateful(t *testing.T, source string, ver uint64, ledger LedgerForLogic) (bool, error) { - ops := testProg(t, source, ver) - - txn := makeSampleTxn() - ep := defaultEvalParams(nil, &txn) - ep.TxnGroup = makeSampleTxnGroup(txn) - _, err := EvalStateful(ops.Program, ep) - require.Error(t, err) - require.Contains(t, err.Error(), "ledger not available") - - ep.Ledger = ledger - return EvalStateful(ops.Program, ep) -} - func TestBalance(t *testing.T) { t.Parallel() + ep, ledger := makeSampleEnv() text := "int 2; balance; int 177; ==" - tl := makeTestLedger( - map[basics.Address]uint64{ - makeSampleTxn().Txn.Receiver: 177, - }, - ) - _, err := testStateful(t, text, AssemblerMaxVersion, tl) - require.Error(t, err) - require.Contains(t, err.Error(), "invalid Account reference") + ledger.newAccount(ep.Txn.Txn.Receiver, 177) + testApp(t, text, ep, "invalid Account reference") text = `int 1; balance; int 177; ==` - pass, err := testStateful(t, text, AssemblerMaxVersion, tl) - require.NoError(t, err) - require.True(t, pass) + testApp(t, text, ep) text = `txn Accounts 1; balance; int 177; ==;` // won't assemble in old version teal testProg(t, text, directRefEnabledVersion-1, expect{2, "balance arg 0 wanted type uint64..."}) // but legal after that - pass, err = testStateful(t, text, directRefEnabledVersion, tl) - require.NoError(t, err) - require.True(t, pass) + testApp(t, text, ep) text = "int 0; balance; int 13; ==" var addr basics.Address copy(addr[:], []byte("aoeuiaoeuiaoeuiaoeuiaoeuiaoeui02")) - tl = makeTestLedger( - map[basics.Address]uint64{ - addr: 13, - }, - ) - pass, err = testStateful(t, text, AssemblerMaxVersion, tl) - require.Error(t, err) - require.Contains(t, err.Error(), "failed to fetch balance") - require.False(t, pass) + ledger.newAccount(addr, 13) + testApp(t, text, ep, "failed to fetch balance") - tl = makeTestLedger( - map[basics.Address]uint64{ - makeSampleTxn().Txn.Sender: 13, - }, - ) - pass, err = testStateful(t, text, AssemblerMaxVersion, tl) - require.NoError(t, err) - require.True(t, pass) + ledger.newAccount(ep.Txn.Txn.Sender, 13) + testApp(t, text, ep) } func testApp(t *testing.T, program string, ep EvalParams, problems ...string) basics.EvalDelta { @@ -793,28 +795,17 @@ func testApp(t *testing.T, program string, ep EvalParams, problems ...string) ba func TestMinBalance(t *testing.T) { t.Parallel() - txn := makeSampleTxn() - txgroup := makeSampleTxnGroup(txn) - ep := defaultEvalParams(nil, nil) - ep.Txn = &txn - ep.TxnGroup = txgroup + ep, ledger := makeSampleEnv() - testApp(t, "int 0; min_balance; int 1001; ==", ep, "ledger not available") - - ledger := makeTestLedger( - map[basics.Address]uint64{ - txn.Txn.Sender: 234, // min_balance 0 is Sender - txn.Txn.Receiver: 123, // Accounts[0] has been packed with the Receiver - }, - ) - ep.Ledger = ledger + ledger.newAccount(ep.Txn.Txn.Sender, 234) + ledger.newAccount(ep.Txn.Txn.Receiver, 123) testApp(t, "int 0; min_balance; int 1001; ==", ep) // Sender makes an asset, min balance goes up - ledger.newAsset(txn.Txn.Sender, 7, basics.AssetParams{Total: 1000}) + ledger.newAsset(ep.Txn.Txn.Sender, 7, basics.AssetParams{Total: 1000}) testApp(t, "int 0; min_balance; int 2002; ==", ep) - schemas := makeSchemas(1, 2, 3, 4) - ledger.newApp(txn.Txn.Sender, 77, schemas) + schemas := makeApp(1, 2, 3, 4) + ledger.newApp(ep.Txn.Txn.Sender, 77, schemas) // create + optin + 10 schema base + 4 ints + 6 bytes (local // and global count b/c newApp opts the creator in) minb := 2*1002 + 10*1003 + 4*1004 + 6*1005 @@ -832,7 +823,7 @@ func TestMinBalance(t *testing.T) { testProg(t, "txn Accounts 1; min_balance; int 1001; ==", directRefEnabledVersion) testApp(t, "txn Accounts 1; min_balance; int 1001; ==", ep) // 1 == Accounts[0] // Receiver opts in - ledger.setHolding(txn.Txn.Receiver, 7, 1, true) + ledger.setHolding(ep.Txn.Txn.Receiver, 7, 1, true) testApp(t, "int 1; min_balance; int 2002; ==", ep) // 1 == Accounts[0] testApp(t, "int 2; min_balance; int 1001; ==", ep, "invalid Account reference 2") @@ -871,7 +862,7 @@ func TestAppCheckOptedIn(t *testing.T) { testApp(t, "int 0; int 100; app_opted_in; int 0; ==", now) // Receiver opted in - ledger.newApp(txn.Txn.Receiver, 100, makeSchemas(0, 0, 0, 0)) + ledger.newApp(txn.Txn.Receiver, 100, basics.AppParams{}) testApp(t, "int 1; int 100; app_opted_in; int 1; ==", now) testApp(t, "int 1; int 2; app_opted_in; int 1; ==", now) testApp(t, "int 1; int 2; app_opted_in; int 0; ==", pre) // in pre, int 2 is an actual app id @@ -880,7 +871,7 @@ func TestAppCheckOptedIn(t *testing.T) { expect{3, "app_opted_in arg 0 wanted type uint64..."}) // Sender opted in - ledger.newApp(txn.Txn.Sender, 100, makeSchemas(0, 0, 0, 0)) + ledger.newApp(txn.Txn.Sender, 100, basics.AppParams{}) testApp(t, "int 0; int 100; app_opted_in; int 1; ==", now) } @@ -937,11 +928,11 @@ int 1` testApp(t, text, now, "no app for account") // Make a different app (not 100) - ledger.newApp(txn.Txn.Receiver, 9999, makeSchemas(0, 0, 0, 0)) + ledger.newApp(txn.Txn.Receiver, 9999, basics.AppParams{}) testApp(t, text, now, "no app for account") // create the app and check the value from ApplicationArgs[0] (protocol.PaymentTx) does not exist - ledger.newApp(txn.Txn.Receiver, 100, makeSchemas(0, 0, 0, 0)) + ledger.newApp(txn.Txn.Receiver, 100, basics.AppParams{}) testApp(t, text, now) text = `int 1 // account idx @@ -971,7 +962,7 @@ byte 0x414c474f "no such address") // check special case account idx == 0 => sender - ledger.newApp(txn.Txn.Sender, 100, makeSchemas(0, 0, 0, 0)) + ledger.newApp(txn.Txn.Sender, 100, basics.AppParams{}) text = `int 0 // account idx int 100 // app id txn ApplicationArgs 0 @@ -989,8 +980,8 @@ byte 0x414c474f "invalid Account reference") // check reading state of other app - ledger.newApp(txn.Txn.Sender, 56, makeSchemas(0, 0, 0, 0)) - ledger.newApp(txn.Txn.Sender, 100, makeSchemas(0, 0, 0, 0)) + ledger.newApp(txn.Txn.Sender, 56, basics.AppParams{}) + ledger.newApp(txn.Txn.Sender, 100, basics.AppParams{}) text = `int 0 // account idx int 56 // app id txn ApplicationArgs 0 @@ -1081,7 +1072,7 @@ byte 0x414c474f testApp(t, text, now, "no such app") // create the app and check the value from ApplicationArgs[0] (protocol.PaymentTx) does not exist - ledger.newApp(txn.Txn.Sender, 100, makeSchemas(0, 0, 0, 1)) + ledger.newApp(txn.Txn.Sender, 100, basics.AppParams{}) testApp(t, text, now, "err opcode") @@ -1227,6 +1218,12 @@ bnz ok error: err ok: +int 0//params +asset_params_get AssetCreator +pop +txn Sender +== +assert int 1 ` @@ -1416,6 +1413,20 @@ intc_1 testApp(t, source, now, "cannot compare ([]byte to uint64)") } +func TestAppParams(t *testing.T) { + t.Parallel() + ep, ledger := makeSampleEnv() + ledger.newAccount(ep.Txn.Txn.Sender, 1) + ledger.newApp(ep.Txn.Txn.Sender, 100, basics.AppParams{}) + + /* app id is in ForeignApps, but does not exist */ + source := "int 56; app_params_get AppExtraProgramPages; int 0; ==; assert; int 0; ==" + testApp(t, source, ep) + /* app id is in ForeignApps, but has zero ExtraProgramPages */ + source = "int 100; app_params_get AppExtraProgramPages; int 1; ==; assert; int 0; ==" + testApp(t, source, ep) +} + func TestAppLocalReadWriteDeleteErrors(t *testing.T) { t.Parallel() @@ -1506,7 +1517,7 @@ intc_1 require.Error(t, err) require.Contains(t, err.Error(), "no app for account") - ledger.newApp(txn.Txn.Sender, 100, makeSchemas(0, 0, 0, 0)) + ledger.newApp(txn.Txn.Sender, 100, basics.AppParams{}) if name == "read" { _, err = EvalStateful(ops.Program, ep) @@ -1546,7 +1557,7 @@ func TestAppLocalStateReadWrite(t *testing.T) { }, ) ep.Ledger = ledger - ledger.newApp(txn.Txn.Sender, 100, makeSchemas(0, 0, 0, 0)) + ledger.newApp(txn.Txn.Sender, 100, basics.AppParams{}) // write int and bytes values source := `int 0 // account @@ -1865,7 +1876,7 @@ int 1 require.Error(t, err) require.Contains(t, err.Error(), "no such app") - ledger.newApp(txn.Txn.Sender, 100, makeSchemas(0, 0, 1, 0)) + ledger.newApp(txn.Txn.Sender, 100, makeApp(0, 0, 1, 0)) // a special test for read if name == "read" { @@ -1960,7 +1971,7 @@ int 0x77 }, ) ep.Ledger = ledger - ledger.newApp(txn.Txn.Sender, 100, makeSchemas(0, 0, 0, 0)) + ledger.newApp(txn.Txn.Sender, 100, basics.AppParams{}) ops, err := AssembleStringWithVersion(source, AssemblerMaxVersion) require.NoError(t, err) @@ -2133,14 +2144,14 @@ byte "myval" }, ) ep.Ledger = ledger - ledger.newApp(txn.Txn.Sender, 100, makeSchemas(0, 0, 0, 0)) + ledger.newApp(txn.Txn.Sender, 100, basics.AppParams{}) delta := testApp(t, source, ep, "no such app") require.Empty(t, delta.GlobalDelta) require.Empty(t, delta.LocalDeltas) - ledger.newApp(txn.Txn.Receiver, 101, makeSchemas(0, 0, 0, 0)) - ledger.newApp(txn.Txn.Receiver, 100, makeSchemas(0, 0, 0, 0)) // this keeps current app id = 100 + ledger.newApp(txn.Txn.Receiver, 101, basics.AppParams{}) + ledger.newApp(txn.Txn.Receiver, 100, basics.AppParams{}) // this keeps current app id = 100 algoValue := basics.TealValue{Type: basics.TealBytesType, Bytes: "myval"} ledger.applications[101].GlobalState["mykey"] = algoValue @@ -2177,7 +2188,7 @@ int 7 }, ) ep.Ledger = ledger - ledger.newApp(txn.Txn.Sender, 100, makeSchemas(0, 0, 0, 0)) + ledger.newApp(txn.Txn.Sender, 100, basics.AppParams{}) delta := testApp(t, source, ep) require.Empty(t, delta.LocalDeltas) @@ -2222,7 +2233,7 @@ int 1 }, ) ep.Ledger = ledger - ledger.newApp(txn.Txn.Sender, 100, makeSchemas(0, 0, 0, 0)) + ledger.newApp(txn.Txn.Sender, 100, basics.AppParams{}) delta := testApp(t, source, ep) require.Len(t, delta.GlobalDelta, 2) @@ -2385,7 +2396,7 @@ int 1 }, ) ep.Ledger = ledger - ledger.newApp(txn.Txn.Sender, 100, makeSchemas(0, 0, 0, 0)) + ledger.newApp(txn.Txn.Sender, 100, basics.AppParams{}) ledger.balances[txn.Txn.Receiver] = makeBalanceRecord(txn.Txn.Receiver, 1) ledger.balances[txn.Txn.Receiver].locals[100] = make(basics.TealKeyValue) @@ -2687,7 +2698,7 @@ func TestReturnTypes(t *testing.T) { Clawback: txn.Txn.Receiver, } ledger.newAsset(txn.Txn.Sender, 1, params) - ledger.newApp(txn.Txn.Sender, 1, makeSchemas(0, 0, 0, 0)) + ledger.newApp(txn.Txn.Sender, 1, basics.AppParams{}) ledger.setTrackedCreatable(0, basics.CreatableLocator{Index: 1}) ledger.balances[txn.Txn.Receiver] = makeBalanceRecord(txn.Txn.Receiver, 1) ledger.balances[txn.Txn.Receiver].locals[1] = make(basics.TealKeyValue) @@ -2729,6 +2740,7 @@ func TestReturnTypes(t *testing.T) { "gtxnsa": "gtxnsa ApplicationArgs 0", "pushint": "pushint 7272", "pushbytes": `pushbytes "jojogoodgorilla"`, + "app_params_get": "app_params_get AppGlobalNumUint", } byName := OpsByName[LogicVersion] @@ -2777,89 +2789,38 @@ func TestReturnTypes(t *testing.T) { } func TestRound(t *testing.T) { - source := `global Round -int 1 ->= -` - ledger := makeTestLedger( - map[basics.Address]uint64{}, - ) - ops, err := AssembleStringWithVersion(source, AssemblerMaxVersion) - require.NoError(t, err) - - ep := defaultEvalParams(nil, nil) - err = CheckStateful(ops.Program, ep) - require.NoError(t, err) - _, err = EvalStateful(ops.Program, ep) - require.Error(t, err) - require.Contains(t, err.Error(), "ledger not available") - - pass, err := Eval(ops.Program, ep) - require.Error(t, err) - require.Contains(t, err.Error(), "not allowed in current mode") - require.False(t, pass) - - ep.Ledger = ledger - pass, err = EvalStateful(ops.Program, ep) - require.NoError(t, err) - require.True(t, pass) + t.Parallel() + ep, _ := makeSampleEnv() + source := "global Round; int 1; >=" + testApp(t, source, ep) } func TestLatestTimestamp(t *testing.T) { - source := `global LatestTimestamp -int 1 ->= -` - ledger := makeTestLedger( - map[basics.Address]uint64{}, - ) - ops, err := AssembleStringWithVersion(source, AssemblerMaxVersion) - require.NoError(t, err) - - ep := defaultEvalParams(nil, nil) - err = CheckStateful(ops.Program, ep) - require.NoError(t, err) - _, err = EvalStateful(ops.Program, ep) - require.Error(t, err) - require.Contains(t, err.Error(), "ledger not available") - - pass, err := Eval(ops.Program, ep) - require.Error(t, err) - require.Contains(t, err.Error(), "not allowed in current mode") - require.False(t, pass) - - ep.Ledger = ledger - pass, err = EvalStateful(ops.Program, ep) - require.NoError(t, err) - require.True(t, pass) + t.Parallel() + ep, _ := makeSampleEnv() + source := "global LatestTimestamp; int 1; >=" + testApp(t, source, ep) } func TestCurrentApplicationID(t *testing.T) { - source := `global CurrentApplicationID -int 42 -== -` - ledger := makeTestLedger( - map[basics.Address]uint64{}, - ) + t.Parallel() + ep, ledger := makeSampleEnv() ledger.appID = basics.AppIndex(42) - ops, err := AssembleStringWithVersion(source, AssemblerMaxVersion) - require.NoError(t, err) + source := "global CurrentApplicationID; int 42; ==" + testApp(t, source, ep) +} - ep := defaultEvalParams(nil, nil) - err = CheckStateful(ops.Program, ep) - require.NoError(t, err) - _, err = EvalStateful(ops.Program, ep) - require.Error(t, err) - require.Contains(t, err.Error(), "ledger not available") +func TestAppLoop(t *testing.T) { + t.Parallel() + ep, _ := makeSampleEnv() - pass, err := Eval(ops.Program, ep) - require.Error(t, err) - require.Contains(t, err.Error(), "not allowed in current mode") - require.False(t, pass) + stateful := "global CurrentApplicationID; pop;" - ep.Ledger = ledger - pass, err = EvalStateful(ops.Program, ep) - require.NoError(t, err) - require.True(t, pass) + // Double until > 10. Should be 16 + testApp(t, stateful+"int 1; loop: int 2; *; dup; int 10; <; bnz loop; int 16; ==", ep) + + testApp(t, stateful+"int 1; loop: int 2; *; dup; int 10; <; bnz loop; int 16; ==", ep) + + // Infinite loop because multiply by one instead of two + testApp(t, stateful+"int 1; loop:; int 1; *; dup; int 10; <; bnz loop; int 16; ==", ep, "dynamic cost") } diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index 465dea6012..d45bcce3fe 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -910,6 +910,10 @@ const globalV4TestProgram = globalV3TestProgram + ` // No new globals in v4 ` +const globalV5TestProgram = globalV4TestProgram + ` +// No new globals in v5 +` + func TestGlobal(t *testing.T) { t.Parallel() type desc struct { @@ -933,6 +937,10 @@ func TestGlobal(t *testing.T) { CreatorAddress, globalV4TestProgram, EvalStateful, CheckStateful, }, + 5: { + CreatorAddress, globalV5TestProgram, + EvalStateful, CheckStateful, + }, } ledger := makeTestLedger(nil) ledger.appID = 42 @@ -940,6 +948,8 @@ func TestGlobal(t *testing.T) { require.NoError(t, err) ledger.creatorAddr = addr for v := uint64(0); v <= AssemblerMaxVersion; v++ { + _, ok := tests[v] + require.True(t, ok) t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) { last := tests[v].lastField testProgram := tests[v].program diff --git a/data/transactions/logic/fields.go b/data/transactions/logic/fields.go index 20004fdced..9bc37547cb 100644 --- a/data/transactions/logic/fields.go +++ b/data/transactions/logic/fields.go @@ -23,7 +23,7 @@ import ( "github.com/algorand/go-algorand/protocol" ) -//go:generate stringer -type=TxnField,GlobalField,AssetParamsField,AssetHoldingField,OnCompletionConstType -output=fields_string.go +//go:generate stringer -type=TxnField,GlobalField,AssetParamsField,AppParamsField,AssetHoldingField,OnCompletionConstType -output=fields_string.go // TxnField is an enum type for `txn` and `gtxn` type TxnField int @@ -425,10 +425,14 @@ const ( AssetFreeze // AssetClawback AssetParams.Clawback AssetClawback + + // AssetCreator is not *in* the Params, but it is uniquely determined. + AssetCreator + invalidAssetParamsField ) -// AssetParamsFieldNames are arguments to the 'asset_holding_get' opcode +// AssetParamsFieldNames are arguments to the 'asset_params_get' opcode var AssetParamsFieldNames []string type assetParamsFieldType struct { @@ -448,6 +452,7 @@ var assetParamsFieldTypeList = []assetParamsFieldType{ {AssetReserve, StackBytes}, {AssetFreeze, StackBytes}, {AssetClawback, StackBytes}, + {AssetCreator, StackBytes}, } // AssetParamsFieldTypes is StackUint64 StackBytes in parallel with AssetParamsFieldNames @@ -455,6 +460,55 @@ var AssetParamsFieldTypes []StackType var assetParamsFields map[string]uint64 +// AppParamsField is an enum for `app_params_get` opcode +type AppParamsField int + +const ( + // AppApprovalProgram AppParams.ApprovalProgram + AppApprovalProgram AppParamsField = iota + // AppClearStateProgram AppParams.ClearStateProgram + AppClearStateProgram + // AppGlobalNumUint AppParams.StateSchemas.GlobalStateSchema.NumUint + AppGlobalNumUint + // AppGlobalNumByteSlice AppParams.StateSchemas.GlobalStateSchema.NumByteSlice + AppGlobalNumByteSlice + // AppLocalNumUint AppParams.StateSchemas.LocalStateSchema.NumUint + AppLocalNumUint + // AppLocalNumByteSlice AppParams.StateSchemas.LocalStateSchema.NumByteSlice + AppLocalNumByteSlice + // AppExtraProgramPages AppParams.ExtraProgramPages + AppExtraProgramPages + + // AppCreator is not *in* the Params, but it is uniquely determined. + AppCreator + + invalidAppParamsField +) + +// AppParamsFieldNames are arguments to the 'app_params_get' opcode +var AppParamsFieldNames []string + +type appParamsFieldType struct { + field AppParamsField + ftype StackType +} + +var appParamsFieldTypeList = []appParamsFieldType{ + {AppApprovalProgram, StackBytes}, + {AppClearStateProgram, StackBytes}, + {AppGlobalNumUint, StackUint64}, + {AppGlobalNumByteSlice, StackUint64}, + {AppLocalNumUint, StackUint64}, + {AppLocalNumByteSlice, StackUint64}, + {AppExtraProgramPages, StackUint64}, + {AppCreator, StackBytes}, +} + +// AppParamsFieldTypes is StackUint64 StackBytes in parallel with AppParamsFieldNames +var AppParamsFieldTypes []StackType + +var appParamsFields map[string]uint64 + func init() { TxnFieldNames = make([]string, int(invalidTxnField)) for fi := Sender; fi < invalidTxnField; fi++ { @@ -515,6 +569,19 @@ func init() { assetParamsFields[fn] = uint64(i) } + AppParamsFieldNames = make([]string, int(invalidAppParamsField)) + for i := AppApprovalProgram; i < invalidAppParamsField; i++ { + AppParamsFieldNames[int(i)] = i.String() + } + AppParamsFieldTypes = make([]StackType, len(AppParamsFieldNames)) + for _, ft := range appParamsFieldTypeList { + AppParamsFieldTypes[int(ft.field)] = ft.ftype + } + appParamsFields = make(map[string]uint64) + for i, fn := range AppParamsFieldNames { + appParamsFields[fn] = uint64(i) + } + txnTypeIndexes = make(map[string]uint64, len(TxnTypeNames)) for i, tt := range TxnTypeNames { txnTypeIndexes[tt] = uint64(i) diff --git a/data/transactions/logic/fields_string.go b/data/transactions/logic/fields_string.go index e56dd872fe..c3fa22576c 100644 --- a/data/transactions/logic/fields_string.go +++ b/data/transactions/logic/fields_string.go @@ -1,4 +1,4 @@ -// Code generated by "stringer -type=TxnField,GlobalField,AssetParamsField,AssetHoldingField,OnCompletionConstType -output=fields_string.go"; DO NOT EDIT. +// Code generated by "stringer -type=TxnField,GlobalField,AssetParamsField,AppParamsField,AssetHoldingField,OnCompletionConstType -output=fields_string.go"; DO NOT EDIT. package logic @@ -120,12 +120,13 @@ func _() { _ = x[AssetReserve-8] _ = x[AssetFreeze-9] _ = x[AssetClawback-10] - _ = x[invalidAssetParamsField-11] + _ = x[AssetCreator-11] + _ = x[invalidAssetParamsField-12] } -const _AssetParamsField_name = "AssetTotalAssetDecimalsAssetDefaultFrozenAssetUnitNameAssetNameAssetURLAssetMetadataHashAssetManagerAssetReserveAssetFreezeAssetClawbackinvalidAssetParamsField" +const _AssetParamsField_name = "AssetTotalAssetDecimalsAssetDefaultFrozenAssetUnitNameAssetNameAssetURLAssetMetadataHashAssetManagerAssetReserveAssetFreezeAssetClawbackAssetCreatorinvalidAssetParamsField" -var _AssetParamsField_index = [...]uint8{0, 10, 23, 41, 54, 63, 71, 88, 100, 112, 123, 136, 159} +var _AssetParamsField_index = [...]uint8{0, 10, 23, 41, 54, 63, 71, 88, 100, 112, 123, 136, 148, 171} func (i AssetParamsField) String() string { if i < 0 || i >= AssetParamsField(len(_AssetParamsField_index)-1) { @@ -133,6 +134,31 @@ func (i AssetParamsField) String() string { } return _AssetParamsField_name[_AssetParamsField_index[i]:_AssetParamsField_index[i+1]] } +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[AppApprovalProgram-0] + _ = x[AppClearStateProgram-1] + _ = x[AppGlobalNumUint-2] + _ = x[AppGlobalNumByteSlice-3] + _ = x[AppLocalNumUint-4] + _ = x[AppLocalNumByteSlice-5] + _ = x[AppExtraProgramPages-6] + _ = x[AppCreator-7] + _ = x[invalidAppParamsField-8] +} + +const _AppParamsField_name = "AppApprovalProgramAppClearStateProgramAppGlobalNumUintAppGlobalNumByteSliceAppLocalNumUintAppLocalNumByteSliceAppExtraProgramPagesAppCreatorinvalidAppParamsField" + +var _AppParamsField_index = [...]uint8{0, 18, 38, 54, 75, 90, 110, 130, 140, 161} + +func (i AppParamsField) String() string { + if i < 0 || i >= AppParamsField(len(_AppParamsField_index)-1) { + return "AppParamsField(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _AppParamsField_name[_AppParamsField_index[i]:_AppParamsField_index[i+1]] +} func _() { // An "invalid array index" compiler error signifies that the constant values have changed. // Re-run the stringer command to generate them again. diff --git a/data/transactions/logic/opcodes.go b/data/transactions/logic/opcodes.go index bd06eb3745..45def2008f 100644 --- a/data/transactions/logic/opcodes.go +++ b/data/transactions/logic/opcodes.go @@ -21,7 +21,7 @@ import ( ) // LogicVersion defines default assembler and max eval versions -const LogicVersion = 4 +const LogicVersion = 5 // rekeyingEnabledVersion is the version of TEAL where RekeyTo functionality // was enabled. This is important to remember so that old TEAL accounts cannot @@ -243,6 +243,7 @@ var OpSpecs = []OpSpec{ {0x70, "asset_holding_get", opAssetHoldingGet, assembleAssetHolding, disAssetHolding, twoInts, oneAny.plus(oneInt), 2, runModeApplication, immediates("i")}, {0x70, "asset_holding_get", opAssetHoldingGet, assembleAssetHolding, disAssetHolding, oneAny.plus(oneInt), oneAny.plus(oneInt), directRefEnabledVersion, runModeApplication, immediates("i")}, {0x71, "asset_params_get", opAssetParamsGet, assembleAssetParams, disAssetParams, oneInt, oneAny.plus(oneInt), 2, runModeApplication, immediates("i")}, + {0x72, "app_params_get", opAppParamsGet, assembleAppParams, disAppParams, oneInt, oneAny.plus(oneInt), 5, runModeApplication, immediates("i")}, {0x78, "min_balance", opMinBalance, asmDefault, disDefault, oneInt, oneInt, 3, runModeApplication, opDefault}, {0x78, "min_balance", opMinBalance, asmDefault, disDefault, oneAny, oneInt, directRefEnabledVersion, runModeApplication, opDefault}, diff --git a/ledger/applications.go b/ledger/applications.go index 6e1674624a..e03604a593 100644 --- a/ledger/applications.go +++ b/ledger/applications.go @@ -106,32 +106,60 @@ func (al *logicLedger) AssetHolding(addr basics.Address, assetIdx basics.AssetIn return holding, nil } -func (al *logicLedger) AssetParams(assetIdx basics.AssetIndex) (basics.AssetParams, error) { +func (al *logicLedger) AssetParams(assetIdx basics.AssetIndex) (basics.AssetParams, basics.Address, error) { // Find asset creator creator, ok, err := al.cow.GetCreator(basics.CreatableIndex(assetIdx), basics.AssetCreatable) if err != nil { - return basics.AssetParams{}, err + return basics.AssetParams{}, creator, err } // Ensure asset exists if !ok { - return basics.AssetParams{}, fmt.Errorf("asset %d does not exist", assetIdx) + return basics.AssetParams{}, creator, fmt.Errorf("asset %d does not exist", assetIdx) } // Fetch the requested balance record record, err := al.cow.Get(creator, false) if err != nil { - return basics.AssetParams{}, err + return basics.AssetParams{}, creator, err } // Ensure account created the requested asset params, ok := record.AssetParams[assetIdx] if !ok { err = fmt.Errorf("account %s has not created asset %d", creator, assetIdx) - return basics.AssetParams{}, err + return basics.AssetParams{}, creator, err } - return params, nil + return params, creator, nil +} + +func (al *logicLedger) AppParams(appIdx basics.AppIndex) (basics.AppParams, basics.Address, error) { + // Find app creator + creator, ok, err := al.cow.GetCreator(basics.CreatableIndex(appIdx), basics.AppCreatable) + if err != nil { + return basics.AppParams{}, creator, err + } + + // Ensure app exists + if !ok { + return basics.AppParams{}, creator, fmt.Errorf("app %d does not exist", appIdx) + } + + // Fetch the requested balance record + record, err := al.cow.Get(creator, false) + if err != nil { + return basics.AppParams{}, creator, err + } + + // Ensure account created the requested app + params, ok := record.AppParams[appIdx] + if !ok { + err = fmt.Errorf("account %s has not created app %d", creator, appIdx) + return basics.AppParams{}, creator, err + } + + return params, creator, nil } func (al *logicLedger) Round() basics.Round { diff --git a/ledger/applications_test.go b/ledger/applications_test.go index 403084d02a..5d1ec743ae 100644 --- a/ledger/applications_test.go +++ b/ledger/applications_test.go @@ -220,15 +220,16 @@ func TestLogicLedgerAsset(t *testing.T) { a.NoError(err) a.NotNil(l) - _, err = l.AssetParams(basics.AssetIndex(aidx)) + _, _, err = l.AssetParams(basics.AssetIndex(aidx)) a.Error(err) a.Contains(err.Error(), fmt.Sprintf("asset %d does not exist", aidx)) c.brs = map[basics.Address]basics.AccountData{ addr1: {AssetParams: map[basics.AssetIndex]basics.AssetParams{assetIdx: {Total: 1000}}}, } - ap, err := l.AssetParams(assetIdx) + ap, creator, err := l.AssetParams(assetIdx) a.NoError(err) + a.Equal(addr1, creator) a.Equal(uint64(1000), ap.Total) _, err = l.AssetHolding(addr1, assetIdx) diff --git a/test/scripts/e2e_subs/teal-app-params.sh b/test/scripts/e2e_subs/teal-app-params.sh new file mode 100755 index 0000000000..89b30d8367 --- /dev/null +++ b/test/scripts/e2e_subs/teal-app-params.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +filename=$(basename "$0") +scriptname="${filename%.*}" +date "+${scriptname} start %Y%m%d_%H%M%S" + +set -e +set -x +set -o pipefail +export SHELLOPTS + +WALLET=$1 + +TEAL=test/scripts/e2e_subs/tealprogs + +gcmd="goal -w ${WALLET}" + +ACCOUNT=$(${gcmd} account list|awk '{ print $3 }') + +APPID=$(${gcmd} app create --creator "$ACCOUNT" --approval-prog=${TEAL}/app-params.teal --clear-prog=${TEAL}/approve-all.teal --global-byteslices 1 --global-ints 2 --local-byteslices 3 --local-ints 4 --extra-pages 2 --app-arg "addr:$ACCOUNT" | grep Created | awk '{ print $6 }') + +ACCOUNTB=$(${gcmd} account new|awk '{ print $6 }') +${gcmd} clerk send -f "$ACCOUNT" -t "$ACCOUNTB" -a 1000000 + +# Now call from a different account +${gcmd} app call --app-id="$APPID" --from="$ACCOUNTB" --app-arg "addr:$ACCOUNT" + + + +date "+${scriptname} OK %Y%m%d_%H%M%S" diff --git a/test/scripts/e2e_subs/tealprogs/app-params.teal b/test/scripts/e2e_subs/tealprogs/app-params.teal new file mode 100644 index 0000000000..e8ee7052e9 --- /dev/null +++ b/test/scripts/e2e_subs/tealprogs/app-params.teal @@ -0,0 +1,78 @@ +#pragma version 5 + // This program checks its own app params, as set up in + // teal-app-params.sh, to confirm they all work. + + + // Check the creator is the address provided + int 0 + app_params_get AppCreator + assert + txn ApplicationArgs 0 + == + assert + + + // Check this program is version 5 + int 0 + app_params_get AppApprovalProgram + assert + int 0 + getbyte + int 5 + == + assert + + // Check it's longer than 15 (don't want to try to check all the bytes) + int 0 + app_params_get AppApprovalProgram + assert + len + int 15 + > + assert + + + // Check the Clear State program precisely + int 0 + app_params_get AppClearStateProgram + assert + byte 0x0220010122 + == + assert + + int 0 + app_params_get AppGlobalNumByteSlice + assert + int 1 + == + assert + + int 0 + app_params_get AppGlobalNumUint + assert + int 2 + == + assert + + int 0 + app_params_get AppLocalNumByteSlice + assert + int 3 + == + assert + + int 0 + app_params_get AppLocalNumUint + assert + int 4 + == + assert + + int 0 + app_params_get AppExtraProgramPages + assert + int 2 + == + assert + + int 1