diff --git a/.gitignore b/.gitignore index 218b3879b8..ebd0ef239b 100644 --- a/.gitignore +++ b/.gitignore @@ -65,3 +65,5 @@ assets index.html +# test summary +testresults.json diff --git a/data/transactions/logic/README.md b/data/transactions/logic/README.md index 6bdc194573..b76869db1b 100644 --- a/data/transactions/logic/README.md +++ b/data/transactions/logic/README.md @@ -165,6 +165,7 @@ various sizes. | `extract_uint16` | pop a byte-array A and integer B. Extract a range of bytes from A starting at B up to but not including B+2, convert bytes as big endian and push the uint64 result. If B+2 is larger than the array length, the program fails | | `extract_uint32` | pop a byte-array A and integer B. Extract a range of bytes from A starting at B up to but not including B+4, convert bytes as big endian and push the uint64 result. If B+4 is larger than the array length, the program fails | | `extract_uint64` | pop a byte-array A and integer B. Extract a range of bytes from A starting at B up to but not including B+8, convert bytes as big endian and push the uint64 result. If B+8 is larger than the array length, the program fails | +| `base64_decode e` | decode X which was base64-encoded using _encoding alphabet_ E. Fail if X is not base64 encoded with alphabet E | These opcodes take byte-array values that are interpreted as big-endian unsigned integers. For mathematical operators, the diff --git a/data/transactions/logic/TEAL_opcodes.md b/data/transactions/logic/TEAL_opcodes.md index 99f020af27..fe667cc781 100644 --- a/data/transactions/logic/TEAL_opcodes.md +++ b/data/transactions/logic/TEAL_opcodes.md @@ -854,6 +854,17 @@ When A is a uint64, index 0 is the least significant bit. Setting bit 3 to 1 on - pop a byte-array A and integer B. Extract a range of bytes from A starting at B up to but not including B+8, convert bytes as big endian and push the uint64 result. If B+8 is larger than the array length, the program fails - LogicSigVersion >= 5 +## base64_decode e + +- Opcode: 0x5c {uint8 alphabet index} +- Pops: *... stack*, []byte +- Pushes: []byte +- decode X which was base64-encoded using _encoding alphabet_ E. Fail if X is not base64 encoded with alphabet E +- **Cost**: 25 +- LogicSigVersion >= 6 + +decodes X using the base64 encoding alphabet E. Specify the alphabet with an immediate arg either as URL and Filename Safe (`URLAlph`) or Standard (`StdAlph`). See RFC 4648 (sections 4 and 5) + ## balance - Opcode: 0x60 diff --git a/data/transactions/logic/assembler.go b/data/transactions/logic/assembler.go index 5b4d72b383..34151d4c0c 100644 --- a/data/transactions/logic/assembler.go +++ b/data/transactions/logic/assembler.go @@ -1244,6 +1244,28 @@ func assembleEcdsa(ops *OpStream, spec *OpSpec, args []string) error { return nil } +func assembleBase64Decode(ops *OpStream, spec *OpSpec, args []string) error { + if len(args) != 1 { + return ops.errorf("%s expects one argument", spec.Name) + } + + alph, ok := base64AlphabetSpecByName[args[0]] + if !ok { + return ops.errorf("%s unknown alphabet: %#v", spec.Name, args[0]) + } + if alph.version > ops.Version { + //nolint:errcheck // we continue to maintain typestack + ops.errorf("%s %s available in version %d. Missed #pragma version?", spec.Name, args[0], alph.version) + } + + val := alph.field + ops.pending.WriteByte(spec.Opcode) + ops.pending.WriteByte(uint8(val)) + ops.trace("%s (%s)", alph.field, alph.ftype) + ops.returns(alph.ftype) + return nil +} + type assembleFunc func(*OpStream, *OpSpec, []string) error // Basic assembly. Any extra bytes of opcode are encoded as byte immediates. @@ -2668,6 +2690,20 @@ func disEcdsa(dis *disassembleState, spec *OpSpec) (string, error) { return fmt.Sprintf("%s %s", spec.Name, EcdsaCurveNames[arg]), nil } +func disBase64Decode(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 + b64dArg := dis.program[dis.pc+1] + if int(b64dArg) >= len(base64AlphabetNames) { + return "", fmt.Errorf("invalid base64_decode arg index %d at pc=%d", b64dArg, dis.pc) + } + return fmt.Sprintf("%s %s", spec.Name, base64AlphabetNames[b64dArg]), nil +} + type disInfo struct { pcOffset []PCOffset hasStatefulOps bool diff --git a/data/transactions/logic/assembler_test.go b/data/transactions/logic/assembler_test.go index 1c78f9d404..19abfefb23 100644 --- a/data/transactions/logic/assembler_test.go +++ b/data/transactions/logic/assembler_test.go @@ -344,6 +344,7 @@ itxna Logs 3 const v6Nonsense = v5Nonsense + ` itxn_next +base64_decode URLAlph ` var nonsense = map[uint64]string{ @@ -361,7 +362,7 @@ var compiled = map[uint64]string{ 3: "032008b7a60cf8acd19181cf959a12f8acd19181cf951af8acd19181cf15f8acd191810f01020026050212340c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d024242047465737400320032013202320328292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e0102222324252104082209240a220b230c240d250e230f23102311231223132314181b1c2b171615400003290349483403350222231d4a484848482a50512a63222352410003420000432105602105612105270463484821052b62482b642b65484821052b2106662b21056721072b682b692107210570004848210771004848361c0037001a0031183119311b311d311e311f3120210721051e312131223123312431253126312731283129312a312b312c312d312e312f4478222105531421055427042106552105082106564c4d4b02210538212106391c0081e80780046a6f686e", 4: "042004010200b7a60c26040242420c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f23102311231223132314181b1c28171615400003290349483403350222231d4a484848482a50512a632223524100034200004322602261222b634848222862482864286548482228236628226724286828692422700048482471004848361c0037001a0031183119311b311d311e311f312024221e312131223123312431253126312731283129312a312b312c312d312e312f44782522531422542b2355220823564c4d4b0222382123391c0081e80780046a6f686e2281d00f24231f880003420001892223902291922394239593a0a1a2a3a4a5a6a7a8a9aaabacadae23af3a00003b003c003d8164", 5: "052004010002b7a60c26050242420c68656c6c6f20776f726c6421070123456789abcd208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292b0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f23102311231223132314181b1c28171615400003290349483403350222231d4a484848482b50512a632223524100034200004322602261222704634848222862482864286548482228246628226723286828692322700048482371004848361c0037001a0031183119311b311d311e311f312023221e312131223123312431253126312731283129312a312b312c312d312e312f447825225314225427042455220824564c4d4b0222382124391c0081e80780046a6f686e2281d00f23241f880003420001892224902291922494249593a0a1a2a3a4a5a6a7a8a9aaabacadae24af3a00003b003c003d816472064e014f012a57000823810858235b235a2359b03139330039b1b200b322c01a23c1001a2323c21a23c3233e233f8120af06002a494905002a49490700b53a03", - 6: "062004010002b7a60c26050242420c68656c6c6f20776f726c6421070123456789abcd208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292b0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f23102311231223132314181b1c28171615400003290349483403350222231d4a484848482b50512a632223524100034200004322602261222704634848222862482864286548482228246628226723286828692322700048482371004848361c0037001a0031183119311b311d311e311f312023221e312131223123312431253126312731283129312a312b312c312d312e312f447825225314225427042455220824564c4d4b0222382124391c0081e80780046a6f686e2281d00f23241f880003420001892224902291922494249593a0a1a2a3a4a5a6a7a8a9aaabacadae24af3a00003b003c003d816472064e014f012a57000823810858235b235a2359b03139330039b1b200b322c01a23c1001a2323c21a23c3233e233f8120af06002a494905002a49490700b53a03b6", + 6: "062004010002b7a60c26050242420c68656c6c6f20776f726c6421070123456789abcd208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292b0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f23102311231223132314181b1c28171615400003290349483403350222231d4a484848482b50512a632223524100034200004322602261222704634848222862482864286548482228246628226723286828692322700048482371004848361c0037001a0031183119311b311d311e311f312023221e312131223123312431253126312731283129312a312b312c312d312e312f447825225314225427042455220824564c4d4b0222382124391c0081e80780046a6f686e2281d00f23241f880003420001892224902291922494249593a0a1a2a3a4a5a6a7a8a9aaabacadae24af3a00003b003c003d816472064e014f012a57000823810858235b235a2359b03139330039b1b200b322c01a23c1001a2323c21a23c3233e233f8120af06002a494905002a49490700b53a03b65c00", } func pseudoOp(opcode string) bool { diff --git a/data/transactions/logic/doc.go b/data/transactions/logic/doc.go index 5468d808c7..ff258f01e3 100644 --- a/data/transactions/logic/doc.go +++ b/data/transactions/logic/doc.go @@ -133,6 +133,7 @@ var opDocByName = map[string]string{ "extract_uint16": "pop a byte-array A and integer B. Extract a range of bytes from A starting at B up to but not including B+2, convert bytes as big endian and push the uint64 result. If B+2 is larger than the array length, the program fails", "extract_uint32": "pop a byte-array A and integer B. Extract a range of bytes from A starting at B up to but not including B+4, convert bytes as big endian and push the uint64 result. If B+4 is larger than the array length, the program fails", "extract_uint64": "pop a byte-array A and integer B. Extract a range of bytes from A starting at B up to but not including B+8, convert bytes as big endian and push the uint64 result. If B+8 is larger than the array length, the program fails", + "base64_decode": "decode X which was base64-encoded using _encoding alphabet_ E. Fail if X is not base64 encoded with alphabet E", "balance": "get 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.", "min_balance": "get minimum required balance for account A, in microalgos. Required balance is affected by [ASA](https://developer.algorand.org/docs/features/asa/#assets-overview) and [App](https://developer.algorand.org/docs/features/asc1/stateful/#minimum-balance-requirement-for-a-smart-contract) 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.", @@ -229,6 +230,8 @@ var opcodeImmediateNotes = map[string]string{ "ecdsa_verify": "{uint8 curve index}", "ecdsa_pk_decompress": "{uint8 curve index}", "ecdsa_pk_recover": "{uint8 curve index}", + + "base64_decode": "{uint8 alphabet index}", } // OpImmediateNote returns a short string about immediate data which follows the op byte @@ -283,6 +286,7 @@ var opDocExtras = map[string]string{ "itxn_begin": "`itxn_begin` initializes Sender to the application address; Fee to the minimum allowable, taking into account MinTxnFee and credit from overpaying in earlier transactions; FirstValid/LastValid to the values in the top-level transaction, and all other fields to zero values.", "itxn_field": "`itxn_field` fails if X is of the wrong type for F, including a byte array of the wrong size for use as an address when F is an address field. `itxn_field` also fails if X is an account or asset that does not appear in `txn.Accounts` or `txn.ForeignAssets` of the top-level transaction. (Setting addresses in asset creation are exempted from this requirement.)", "itxn_submit": "`itxn_submit` resets the current transaction so that it can not be resubmitted. A new `itxn_begin` is required to prepare another inner transaction.", + "base64_decode": "decodes X using the base64 encoding alphabet E. Specify the alphabet with an immediate arg either as URL and Filename Safe (`URLAlph`) or Standard (`StdAlph`). See RFC 4648 (sections 4 and 5)", } // OpDocExtra returns extra documentation text about an op @@ -295,7 +299,7 @@ func OpDocExtra(opName string) string { // opcodes consecutively, even if their opcode values are not. var OpGroups = map[string][]string{ "Arithmetic": {"sha256", "keccak256", "sha512_256", "ed25519verify", "ecdsa_verify", "ecdsa_pk_recover", "ecdsa_pk_decompress", "+", "-", "/", "*", "<", ">", "<=", ">=", "&&", "||", "shl", "shr", "sqrt", "bitlen", "exp", "==", "!=", "!", "len", "itob", "btoi", "%", "|", "&", "^", "~", "mulw", "addw", "divmodw", "expw", "getbit", "setbit", "getbyte", "setbyte", "concat"}, - "Byte Array Slicing": {"substring", "substring3", "extract", "extract3", "extract_uint16", "extract_uint32", "extract_uint64"}, + "Byte Array Slicing": {"substring", "substring3", "extract", "extract3", "extract_uint16", "extract_uint32", "extract_uint64", "base64_decode"}, "Byte Array Arithmetic": {"b+", "b-", "b/", "b*", "b<", "b>", "b<=", "b>=", "b==", "b!=", "b%"}, "Byte Array Logic": {"b|", "b&", "b^", "b~"}, "Loading Values": {"intcblock", "intc", "intc_0", "intc_1", "intc_2", "intc_3", "pushint", "bytecblock", "bytec", "bytec_0", "bytec_1", "bytec_2", "bytec_3", "pushbytes", "bzero", "arg", "arg_0", "arg_1", "arg_2", "arg_3", "args", "txn", "gtxn", "txna", "txnas", "gtxna", "gtxnas", "gtxns", "gtxnsa", "gtxnsas", "global", "load", "loads", "store", "stores", "gload", "gloads", "gaid", "gaids"}, diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 9a2530f363..cc7006f03a 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -20,6 +20,7 @@ import ( "bytes" "crypto/sha256" "crypto/sha512" + "encoding/base64" "encoding/binary" "encoding/hex" "errors" @@ -4034,3 +4035,28 @@ func (cx *EvalContext) PcDetails() (pc int, dis string) { } return cx.pc, dis } + +func base64Decode(encoded []byte, encoding *base64.Encoding) ([]byte, error) { + decoded := make([]byte, encoding.DecodedLen(len(encoded))) + n, err := encoding.Strict().Decode(decoded, encoded) + if err != nil { + return decoded[:0], err + } + return decoded[:n], err +} + +func opBase64Decode(cx *EvalContext) { + last := len(cx.stack) - 1 + alphabetField := Base64Alphabet(cx.program[cx.pc+1]) + fs, ok := base64AlphabetSpecByField[alphabetField] + if !ok || fs.version > cx.version { + cx.err = fmt.Errorf("invalid base64_decode alphabet %d", alphabetField) + return + } + + encoding := base64.URLEncoding + if alphabetField == StdAlph { + encoding = base64.StdEncoding + } + cx.stack[last].Bytes, cx.err = base64Decode(cx.stack[last].Bytes, encoding) +} diff --git a/data/transactions/logic/evalStateful_test.go b/data/transactions/logic/evalStateful_test.go index 5a212629c6..2098d3af41 100644 --- a/data/transactions/logic/evalStateful_test.go +++ b/data/transactions/logic/evalStateful_test.go @@ -2391,7 +2391,8 @@ func TestReturnTypes(t *testing.T) { "args": "args", "itxn": "itxn_begin; int pay; itxn_field TypeEnum; itxn_submit; itxn CreatedAssetID", // This next one is a cop out. Can't use itxna Logs until we have inner appl - "itxna": "itxn_begin; int pay; itxn_field TypeEnum; itxn_submit; itxn NumLogs", + "itxna": "itxn_begin; int pay; itxn_field TypeEnum; itxn_submit; itxn NumLogs", + "base64_decode": `pushbytes "YWJjMTIzIT8kKiYoKSctPUB+"; base64_decode StdAlph; pushbytes "abc123!?$*&()'-=@~"; ==; pushbytes "YWJjMTIzIT8kKiYoKSctPUB-"; base64_decode URLAlph; pushbytes "abc123!?$*&()'-=@~"; ==; &&; assert`, } // these require special input data and tested separately diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index 74b9f931cb..c374a3022e 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -3719,6 +3719,54 @@ func BenchmarkBigMath(b *testing.B) { } } +func BenchmarkBase64Decode(b *testing.B) { + smallStd := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" + smallURL := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_" + medStd := strings.Repeat(smallStd, 16) + medURL := strings.Repeat(smallURL, 16) + bigStd := strings.Repeat(medStd, 4) + bigURL := strings.Repeat(medURL, 4) + + tags := []string{"small", "medium", "large"} + stds := []string{smallStd, medStd, bigStd} + urls := []string{smallURL, medURL, bigURL} + ops := []string{ + "", + "len", + "b~", + "int 1; pop", + "keccak256", + "sha256", + "sha512_256", + "base64_decode StdAlph", + "base64_decode URLAlph", + } + benches := [][]string{} + for i, tag := range tags { + for _, op := range ops { + testName := op + encoded := stds[i] + if op == "base64_decode URLAlph" { + encoded = urls[i] + } + if len(op) > 0 { + op += "; " + } + op += "pop" + benches = append(benches, []string{ + fmt.Sprintf("%s_%s", testName, tag), + "", + fmt.Sprintf(`byte "%s"; %s`, encoded, op), + "int 1", + }) + } + } + for _, bench := range benches { + b.Run(bench[0], func(b *testing.B) { + benchmarkOperation(b, bench[1], bench[2], bench[3]) + }) + } +} func BenchmarkAddx64(b *testing.B) { progs := [][]string{ {"add long stack", addBenchmarkSource}, @@ -4909,3 +4957,115 @@ func TestPcDetails(t *testing.T) { }) } } + +var minB64DecodeVersion uint64 = 6 + +type b64DecodeTestCase struct { + Encoded string + IsURL bool + Decoded string + Error error +} + +var testCases = []b64DecodeTestCase{ + {"TU9CWS1ESUNLOwoKb3IsIFRIRSBXSEFMRS4KCgpCeSBIZXJtYW4gTWVsdmlsbGU=", + false, + `MOBY-DICK; + +or, THE WHALE. + + +By Herman Melville`, + nil, + }, + {"TU9CWS1ESUNLOwoKb3IsIFRIRSBXSEFMRS4KCgpCeSBIZXJtYW4gTWVsdmlsbGU=", + true, + `MOBY-DICK; + +or, THE WHALE. + + +By Herman Melville`, + nil, + }, + {"YWJjMTIzIT8kKiYoKSctPUB+", false, "abc123!?$*&()'-=@~", nil}, + {"YWJjMTIzIT8kKiYoKSctPUB-", true, "abc123!?$*&()'-=@~", nil}, + {"YWJjMTIzIT8kKiYoKSctPUB+", true, "", base64.CorruptInputError(23)}, + {"YWJjMTIzIT8kKiYoKSctPUB-", false, "", base64.CorruptInputError(23)}, +} + +func TestBase64DecodeFunc(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + for _, testCase := range testCases { + encoding := base64.StdEncoding + if testCase.IsURL { + encoding = base64.URLEncoding + } + encoding = encoding.Strict() + decoded, err := base64Decode([]byte(testCase.Encoded), encoding) + require.Equal(t, []byte(testCase.Decoded), decoded) + require.Equal(t, testCase.Error, err) + } +} + +type b64DecodeTestArgs struct { + Raw []byte + Encoded []byte + IsURL bool + Program []byte +} + +func b64TestDecodeAssembleWithArgs(t *testing.T) []b64DecodeTestArgs { + sourceTmpl := `#pragma version %d +arg 0 +arg 1 +base64_decode %s +==` + args := []b64DecodeTestArgs{} + for _, testCase := range testCases { + if testCase.Error == nil { + field := "StdAlph" + if testCase.IsURL { + field = "URLAlph" + } + source := fmt.Sprintf(sourceTmpl, minB64DecodeVersion, field) + ops, err := AssembleStringWithVersion(source, minB64DecodeVersion) + require.NoError(t, err) + + arg := b64DecodeTestArgs{ + Raw: []byte(testCase.Decoded), + Encoded: []byte(testCase.Encoded), + IsURL: testCase.IsURL, + Program: ops.Program, + } + args = append(args, arg) + } + } + return args +} + +func b64TestDecodeEval(tb testing.TB, args []b64DecodeTestArgs) { + for _, data := range args { + var txn transactions.SignedTxn + txn.Lsig.Logic = data.Program + txn.Lsig.Args = [][]byte{data.Raw[:], data.Encoded[:]} + ep := defaultEvalParams(&strings.Builder{}, &txn) + pass, err := Eval(data.Program, ep) + if err != nil { + require.NoError(tb, err) + } + if !pass { + fmt.Printf("FAILING WITH data = %#v", data) + require.True(tb, pass) + } + } +} + +func TestOpBase64Decode(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + args := b64TestDecodeAssembleWithArgs(t) + b64TestDecodeEval(t, args) +} diff --git a/data/transactions/logic/fields.go b/data/transactions/logic/fields.go index 68ae0f12f8..37ef079cc5 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,AppParamsField,AssetHoldingField,OnCompletionConstType,EcdsaCurve -output=fields_string.go +//go:generate stringer -type=TxnField,GlobalField,AssetParamsField,AppParamsField,AssetHoldingField,OnCompletionConstType,EcdsaCurve,Base64Alphabet -output=fields_string.go // TxnField is an enum type for `txn` and `gtxn` type TxnField int @@ -448,6 +448,44 @@ func (s ecDsaCurveNameSpecMap) getExtraFor(name string) (extra string) { return } +// Base64Alphabet is an enum for the `base64decode` opcode +type Base64Alphabet int + +const ( + // URLAlph represents the base64url alphabet defined in https://www.rfc-editor.org/rfc/rfc4648.html + URLAlph Base64Alphabet = iota + // StdAlph represents the standard alphabet of the RFC + StdAlph + invalidBase64Alphabet +) + +// After running `go generate` these strings will be available: +var base64AlphabetNames [2]string = [...]string{URLAlph.String(), StdAlph.String()} + +type base64AlphabetSpec struct { + field Base64Alphabet + ftype StackType + version uint64 +} + +var base64AlphbetSpecs = []base64AlphabetSpec{ + {URLAlph, StackBytes, 6}, + {StdAlph, StackBytes, 6}, +} + +var base64AlphabetSpecByField map[Base64Alphabet]base64AlphabetSpec +var base64AlphabetSpecByName base64AlphabetSpecMap + +type base64AlphabetSpecMap map[string]base64AlphabetSpec + +func (s base64AlphabetSpecMap) getExtraFor(name string) (extra string) { + // Uses 6 here because base64_decode fields were introduced in 6 + if s[name].version > 6 { + extra = fmt.Sprintf("LogicSigVersion >= %d.", s[name].version) + } + return +} + // AssetHoldingField is an enum for `asset_holding_get` opcode type AssetHoldingField int @@ -681,6 +719,16 @@ func init() { ecdsaCurveSpecByName[ahfn] = ecdsaCurveSpecByField[EcdsaCurve(i)] } + base64AlphabetSpecByField = make(map[Base64Alphabet]base64AlphabetSpec, len(base64AlphabetNames)) + for _, s := range base64AlphbetSpecs { + base64AlphabetSpecByField[s.field] = s + } + + base64AlphabetSpecByName = make(base64AlphabetSpecMap, len(base64AlphabetNames)) + for i, alphname := range base64AlphabetNames { + base64AlphabetSpecByName[alphname] = base64AlphabetSpecByField[Base64Alphabet(i)] + } + AssetHoldingFieldNames = make([]string, int(invalidAssetHoldingField)) for i := AssetBalance; i < invalidAssetHoldingField; i++ { AssetHoldingFieldNames[int(i)] = i.String() diff --git a/data/transactions/logic/fields_string.go b/data/transactions/logic/fields_string.go index 82abacb941..5861dedc17 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,AppParamsField,AssetHoldingField,OnCompletionConstType,EcdsaCurve -output=fields_string.go"; DO NOT EDIT. +// Code generated by "stringer -type=TxnField,GlobalField,AssetParamsField,AppParamsField,AssetHoldingField,OnCompletionConstType,EcdsaCurve,Base64Alphabet -output=fields_string.go"; DO NOT EDIT. package logic @@ -227,3 +227,22 @@ func (i EcdsaCurve) String() string { } return _EcdsaCurve_name[_EcdsaCurve_index[i]:_EcdsaCurve_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[URLAlph-0] + _ = x[StdAlph-1] + _ = x[invalidBase64Alphabet-2] +} + +const _Base64Alphabet_name = "URLAlphStdAlphinvalidBase64Alphabet" + +var _Base64Alphabet_index = [...]uint8{0, 7, 14, 35} + +func (i Base64Alphabet) String() string { + if i < 0 || i >= Base64Alphabet(len(_Base64Alphabet_index)-1) { + return "Base64Alphabet(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _Base64Alphabet_name[_Base64Alphabet_index[i]:_Base64Alphabet_index[i+1]] +} diff --git a/data/transactions/logic/opcodes.go b/data/transactions/logic/opcodes.go index 604db789a2..11f1b258f7 100644 --- a/data/transactions/logic/opcodes.go +++ b/data/transactions/logic/opcodes.go @@ -237,6 +237,7 @@ var OpSpecs = []OpSpec{ {0x4e, "cover", opCover, asmDefault, disDefault, oneAny, oneAny, 5, modeAny, stacky(typeCover, "n")}, {0x4f, "uncover", opUncover, asmDefault, disDefault, oneAny, oneAny, 5, modeAny, stacky(typeUncover, "n")}, + // byteslice processing / StringOps {0x50, "concat", opConcat, asmDefault, disDefault, twoBytes, oneBytes, 2, modeAny, opDefault}, {0x51, "substring", opSubstring, assembleSubstring, disDefault, oneBytes, oneBytes, 2, modeAny, immediates("s", "e")}, {0x52, "substring3", opSubstring3, asmDefault, disDefault, byteIntInt, oneBytes, 2, modeAny, opDefault}, @@ -249,6 +250,7 @@ var OpSpecs = []OpSpec{ {0x59, "extract_uint16", opExtract16Bits, asmDefault, disDefault, byteInt, oneInt, 5, modeAny, opDefault}, {0x5a, "extract_uint32", opExtract32Bits, asmDefault, disDefault, byteInt, oneInt, 5, modeAny, opDefault}, {0x5b, "extract_uint64", opExtract64Bits, asmDefault, disDefault, byteInt, oneInt, 5, modeAny, opDefault}, + {0x5c, "base64_decode", opBase64Decode, assembleBase64Decode, disBase64Decode, oneBytes, oneBytes, 6, modeAny, costlyImm(25, "e")}, {0x60, "balance", opBalance, asmDefault, disDefault, oneInt, oneInt, 2, runModeApplication, opDefault}, {0x60, "balance", opBalance, asmDefault, disDefault, oneAny, oneInt, directRefEnabledVersion, runModeApplication, opDefault},