// Copyright (c) 2013-2017 The btcsuite developers // Copyright (c) 2015-2021 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. package txscript import ( "bytes" "errors" "fmt" "testing" "github.com/decred/dcrd/chaincfg/chainhash" ) // TestGetSigOpCount tests that the GetSigOpCount function behaves as expected. func TestGetSigOpCount(t *testing.T) { // This should correspond to MaxPubKeysPerMultiSig. It's intentionally // not referred to here so that any changes to MaxPubKeysPerMultisig // are flagged during tests. maxMultiSigOps := 20 // Build out a script that tests every opcode not on the "special list" // below. These will be tested separately. specialOpCodes := map[byte]struct{}{ OP_CHECKSIG: {}, OP_CHECKSIGVERIFY: {}, OP_CHECKSIGALT: {}, OP_CHECKSIGALTVERIFY: {}, OP_CHECKMULTISIG: {}, OP_CHECKMULTISIGVERIFY: {}, OP_TADD: {}, OP_TGEN: {}, OP_TSPEND: {}, } otherOpCodesScript := "" for i := 0; i <= 255; i++ { if _, ok := specialOpCodes[byte(i)]; ok { continue } otherOpCodesScript = fmt.Sprintf("%s 0x%.2x", otherOpCodesScript, i) } testCases := []struct { name string script string wantCount int wantTreasuryCount int }{{ name: "all opcodes that dont count for a sigop", script: otherOpCodesScript, wantCount: 0, wantTreasuryCount: 0, }, { name: "all opcodes that count for a sigop", script: "CHECKSIG CHECKSIGVERIFY CHECKSIGALT CHECKSIGALTVERIFY", wantCount: 4, wantTreasuryCount: 4, }, { name: "multisig with < MaxPubKeysPerMultiSig", script: "2 DATA_33 0x00{33} 0x00{33} 0x00{33} 3 OP_CHECKMULTISIG", wantCount: maxMultiSigOps, wantTreasuryCount: maxMultiSigOps, }, { name: "multisigverify with less than MaxPubKeysPerMultiSig", script: "2 DATA_33 0x00{33} 0x00{33} 0x00{33} 3 OP_CHECKMULTISIGVERIFY", wantCount: maxMultiSigOps, wantTreasuryCount: maxMultiSigOps, }, { name: "valid p2pkh output", script: "DUP HASH160 DATA_20 0x00{20} EQUALVERIFY CHECKSIG", wantCount: 1, wantTreasuryCount: 1, }, { name: "valid p2sh output", script: "HASH160 DATA_20 0x00{20} EQUAL", wantCount: 0, wantTreasuryCount: 0, }, { name: "valid stake change output", script: "SSTXCHANGE DUP HASH160 DATA_20 0x00{20} EQUALVERIFY CHECKSIG", wantCount: 1, wantTreasuryCount: 1, }, { name: "valid tspend output", script: "TGEN DUP HASH160 DATA_20 0x00{20} EQUALVERIFY CHECKSIG", wantCount: 1, wantTreasuryCount: 1, }, { name: "valid tspend input", script: "DATA_64 0x00{64} TSPEND", wantCount: 0, wantTreasuryCount: 1, }, { name: "valid tadd output", script: "TADD", wantCount: 0, wantTreasuryCount: 0, }} for i := range testCases { tc := &testCases[i] t.Run(tc.name, func(t *testing.T) { script := mustParseShortFormV0(tc.script) gotCount := GetSigOpCount(script, false) if gotCount != tc.wantCount { t.Fatalf("unexpected sigOpCount with treasury=false. "+ "want=%d got=%d", tc.wantCount, gotCount) } gotTreasuryCount := GetSigOpCount(script, true) if gotTreasuryCount != tc.wantTreasuryCount { t.Fatalf("unexpected sigOpCount with treasury=true. "+ "want=%d got=%d", tc.wantTreasuryCount, gotTreasuryCount) } }) } } // TestGetPreciseSigOps ensures the more precise signature operation counting // mechanism which includes signatures in P2SH scripts works as expected. func TestGetPreciseSigOps(t *testing.T) { t.Parallel() tests := []struct { name string scriptSig []byte nSigOps int }{{ name: "scriptSig doesn't parse", scriptSig: mustParseShortFormV0("PUSHDATA1 0x02"), }, { name: "scriptSig isn't push only", scriptSig: mustParseShortFormV0("1 DUP"), nSigOps: 0, }, { name: "scriptSig length 0", scriptSig: nil, nSigOps: 0, }, { // No script at end but still push only. name: "No script at the end", scriptSig: mustParseShortFormV0("1 1"), nSigOps: 0, }, { name: "pushed script doesn't parse", scriptSig: mustParseShortFormV0("DATA_2 PUSHDATA1 0x02"), }} // The signature in the p2sh script is nonsensical for the tests since // this script will never be executed. What matters is that it matches // the right pattern. Without treasury enabled. pkScript := mustParseShortFormV0("HASH160 DATA_20 0x433ec2ac1ffa1b7b7d0" + "27f564529c57197f9ae88 EQUAL") for _, test := range tests { count := GetPreciseSigOpCount(test.scriptSig, pkScript, noTreasury) if count != test.nSigOps { t.Errorf("%s: expected count of %d, got %d", test.name, test.nSigOps, count) } } // The signature in the p2sh script is nonsensical for the tests since // this script will never be executed. What matters is that it matches // the right pattern. With treasury enabled. pkScript = mustParseShortFormV0("HASH160 DATA_20 0x433ec2ac1ffa1b7b7d0" + "27f564529c57197f9ae88 EQUAL") for _, test := range tests { count := GetPreciseSigOpCount(test.scriptSig, pkScript, withTreasury) if count != test.nSigOps { t.Errorf("%s: expected count of %d, got %d", test.name, test.nSigOps, count) } } } // TestRemoveOpcodeByData ensures that removing data carrying opcodes based on // the data they contain works as expected. func TestRemoveOpcodeByData(t *testing.T) { t.Parallel() tests := []struct { name string before []byte remove []byte err error after []byte }{{ name: "nothing to do", before: mustParseShortFormV0("NOP"), remove: []byte{1, 2, 3, 4}, after: mustParseShortFormV0("NOP"), }, { name: "simple case", before: mustParseShortFormV0("DATA_4 0x01020304"), remove: []byte{1, 2, 3, 4}, after: nil, }, { name: "simple case (miss)", before: mustParseShortFormV0("DATA_4 0x01020304"), remove: []byte{1, 2, 3, 5}, after: mustParseShortFormV0("DATA_4 0x01020304"), }, { name: "stakesubmission simple case p2pkh", before: mustParseShortFormV0("SSTX DUP HASH160 DATA_20 0x00{16} " + "0x01020304 EQUALVERIFY CHECKSIG"), remove: []byte{1, 2, 3, 4}, after: mustParseShortFormV0("SSTX DUP HASH160 EQUALVERIFY CHECKSIG"), }, { name: "stakesubmission simple case p2pkh (miss)", before: mustParseShortFormV0("SSTX DUP HASH160 DATA_20 0x00{16} " + "0x01020304 EQUALVERIFY CHECKSIG"), remove: []byte{1, 2, 3, 4, 5}, after: mustParseShortFormV0("SSTX DUP HASH160 DATA_20 0x00{16} " + "0x01020304 EQUALVERIFY CHECKSIG"), }, { name: "stakesubmission simple case p2sh", before: mustParseShortFormV0("SSTX HASH160 DATA_20 0x00{16} 0x01020304 " + "EQUAL"), remove: []byte{1, 2, 3, 4}, after: mustParseShortFormV0("SSTX HASH160 EQUAL"), }, { name: "stakesubmission simple case p2sh (miss)", before: mustParseShortFormV0("SSTX HASH160 DATA_20 0x00{16} 0x01020304 " + "EQUAL"), remove: []byte{1, 2, 3, 4, 5}, after: mustParseShortFormV0("SSTX HASH160 DATA_20 0x00{16} " + "0x01020304 EQUAL"), }, { name: "stakegen simple case p2pkh", before: mustParseShortFormV0("SSGEN DUP HASH160 DATA_20 0x00{16} " + "0x01020304 EQUALVERIFY CHECKSIG"), remove: []byte{1, 2, 3, 4}, after: mustParseShortFormV0("SSGEN DUP HASH160 EQUALVERIFY CHECKSIG"), }, { name: "stakegen simple case p2pkh (miss)", before: mustParseShortFormV0("SSGEN DUP HASH160 DATA_20 0x00{16} " + "0x01020304 EQUALVERIFY CHECKSIG"), remove: []byte{1, 2, 3, 4, 5}, after: mustParseShortFormV0("SSGEN DUP HASH160 DATA_20 0x00{16} " + "0x01020304 EQUALVERIFY CHECKSIG"), }, { name: "stakegen simple case p2sh", before: mustParseShortFormV0("SSGEN HASH160 DATA_20 0x00{16} " + "0x01020304 EQUAL"), remove: []byte{1, 2, 3, 4}, after: mustParseShortFormV0("SSGEN HASH160 EQUAL"), }, { name: "stakegen simple case p2sh (miss)", before: mustParseShortFormV0("SSGEN HASH160 DATA_20 0x00{16} " + "0x01020304 EQUAL"), remove: []byte{1, 2, 3, 4, 5}, after: mustParseShortFormV0("SSGEN HASH160 DATA_20 0x00{16} " + "0x01020304 EQUAL"), }, { name: "stakerevoke simple case p2pkh", before: mustParseShortFormV0("SSRTX DUP HASH160 DATA_20 0x00{16} " + "0x01020304 EQUALVERIFY CHECKSIG"), remove: []byte{1, 2, 3, 4}, after: []byte{OP_SSRTX, OP_DUP, OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG}, }, { name: "stakerevoke simple case p2pkh (miss)", before: mustParseShortFormV0("SSRTX DUP HASH160 DATA_20 0x00{20} " + "EQUALVERIFY CHECKSIG"), remove: bytes.Repeat([]byte{0}, 21), after: mustParseShortFormV0("SSRTX DUP HASH160 DATA_20 0x00{20} " + "EQUALVERIFY CHECKSIG"), }, { name: "stakerevoke simple case p2sh", before: mustParseShortFormV0("SSRTX HASH160 DATA_20 0x00{16} " + "0x01020304 EQUAL"), remove: []byte{1, 2, 3, 4}, after: mustParseShortFormV0("SSRTX HASH160 EQUAL"), }, { name: "stakerevoke simple case p2sh (miss)", before: mustParseShortFormV0("SSRTX HASH160 DATA_20 0x00{16} " + "0x01020304 EQUAL"), remove: []byte{1, 2, 3, 4, 5}, after: mustParseShortFormV0("SSRTX HASH160 DATA_20 0x00{16} " + "0x01020304 EQUAL"), }, { // padded to keep it canonical. name: "simple case (pushdata1)", before: mustParseShortFormV0("PUSHDATA1 0x4c 0x00{72} 0x01020304"), remove: []byte{1, 2, 3, 4}, after: nil, }, { name: "simple case (pushdata1 miss)", before: mustParseShortFormV0("PUSHDATA1 0x4c 0x00{72} 0x01020304"), remove: []byte{1, 2, 3, 5}, after: mustParseShortFormV0("PUSHDATA1 0x4c 0x00{72} 0x01020304"), }, { name: "simple case (pushdata1 miss noncanonical)", before: mustParseShortFormV0("PUSHDATA1 0x04 0x01020304"), remove: []byte{1, 2, 3, 4}, after: mustParseShortFormV0("PUSHDATA1 0x04 0x01020304"), }, { name: "simple case (pushdata2)", before: mustParseShortFormV0("PUSHDATA2 0x0001 0x00{252} 0x01020304"), remove: []byte{1, 2, 3, 4}, after: nil, }, { name: "simple case (pushdata2 miss)", before: mustParseShortFormV0("PUSHDATA2 0x0001 0x00{252} 0x01020304"), remove: []byte{1, 2, 3, 4, 5}, after: mustParseShortFormV0("PUSHDATA2 0x0001 0x00{252} 0x01020304"), }, { name: "simple case (pushdata2 miss noncanonical)", before: mustParseShortFormV0("PUSHDATA2 0x0400 0x01020304"), remove: []byte{1, 2, 3, 4}, after: mustParseShortFormV0("PUSHDATA2 0x0400 0x01020304"), }, { // This is padded to make the push canonical. name: "simple case (pushdata4)", before: mustParseShortFormV0("PUSHDATA4 0x00000100 0x00{65532} " + "0x01020304"), remove: []byte{1, 2, 3, 4}, after: nil, }, { name: "simple case (pushdata4 miss noncanonical)", before: mustParseShortFormV0("PUSHDATA4 0x04000000 0x01020304"), remove: []byte{1, 2, 3, 4}, after: mustParseShortFormV0("PUSHDATA4 0x04000000 0x01020304"), }, { // This is padded to make the push canonical. name: "simple case (pushdata4 miss)", before: mustParseShortFormV0("PUSHDATA4 0x00000100 0x00{65532} " + "0x01020304"), remove: []byte{1, 2, 3, 4, 5}, after: mustParseShortFormV0("PUSHDATA4 0x00000100 0x00{65532} " + "0x01020304"), }, { name: "invalid opcode", before: []byte{OP_UNKNOWN240}, remove: []byte{1, 2, 3, 4}, after: []byte{OP_UNKNOWN240}, }, { name: "invalid length (instruction)", before: []byte{OP_PUSHDATA1}, remove: []byte{1, 2, 3, 4}, err: ErrMalformedPush, }, { name: "invalid length (data)", before: []byte{OP_PUSHDATA1, 255, 254}, remove: []byte{1, 2, 3, 4}, err: ErrMalformedPush, }} // tstRemoveOpcodeByData is a convenience function to ensure the provided // script parses before attempting to remove the passed data. const scriptVersion = 0 tstRemoveOpcodeByData := func(script []byte, data []byte) ([]byte, error) { if err := checkScriptParses(scriptVersion, script); err != nil { return nil, err } return removeOpcodeByData(script, data), nil } for _, test := range tests { result, err := tstRemoveOpcodeByData(test.before, test.remove) if !errors.Is(err, test.err) { t.Errorf("%s: unexpected error -- got %v, want %v", test.name, err, test.err) continue } if !bytes.Equal(result, test.after) { t.Errorf("%s: value does not equal expected -- got: %x, want %x", test.name, result, test.after) } } } // TestIsPayToScriptHash ensures the IsPayToScriptHash function returns the // expected results. func TestIsPayToScriptHash(t *testing.T) { t.Parallel() // Convience function that combines fmt.Sprintf with mustParseShortFormV0 // to create more compact tests. p := func(format string, a ...interface{}) []byte { return mustParseShortFormV0(fmt.Sprintf(format, a...)) } // Script hash for a 2-of-3 multisig composed of the following public keys: // pk1: 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 // pk2: 02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9 // pk3: 03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556 p2sh := "f86b5a7c6d32566aa4dccc04d1533530b4d64cf3" tests := []struct { name string // test description script []byte // script to examine want bool // expected p2sh? }{{ name: "almost v0 p2sh -- wrong hash length", script: p("HASH160 DATA_21 0x00%s EQUAL", p2sh), want: false, }, { name: "almost v0 p2sh -- trailing opcode", script: p("HASH160 DATA_20 0x%s EQUAL TRUE", p2sh), want: false, }, { name: "v0 p2sh", script: p("HASH160 DATA_20 0x%s EQUAL", p2sh), want: true, }} for _, test := range tests { got := IsPayToScriptHash(test.script) if got != test.want { t.Errorf("%q: unexpected result -- got %v, want %v", test.name, got, test.want) } } } // TestIsAnyKindOfScriptHash ensures the isAnyKindOfScriptHash function returns // the expected results. func TestIsAnyKindOfScriptHash(t *testing.T) { t.Parallel() // Convience function that combines fmt.Sprintf with mustParseShortFormV0 // to create more compact tests. p := func(format string, a ...interface{}) []byte { return mustParseShortFormV0(fmt.Sprintf(format, a...)) } // Script hash for a 2-of-3 multisig composed of the following public keys: // pk1: 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 // pk2: 02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9 // pk3: 03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556 p2sh := "f86b5a7c6d32566aa4dccc04d1533530b4d64cf3" tests := []struct { name string // test description script []byte // script to examine isTrsy bool // whether script involves a treasury opcode want bool // expected result }{{ name: "almost v0 p2sh -- wrong hash length", script: p("HASH160 DATA_21 0x00%s EQUAL", p2sh), want: false, }, { name: "almost v0 p2sh -- trailing opcode", script: p("HASH160 DATA_20 0x%s EQUAL TRUE", p2sh), want: false, }, { name: "v0 p2sh", script: p("HASH160 DATA_20 0x%s EQUAL", p2sh), want: true, }, { name: "almost v0 stake submission p2sh -- wrong hash length", script: p("SSTX HASH160 DATA_21 0x00%s EQUAL", p2sh), want: false, }, { name: "almost v0 stake submission p2sh -- trailing opcode", script: p("SSTX HASH160 DATA_20 0x%s EQUAL TRUE", p2sh), want: false, }, { name: "v0 stake submission p2sh", script: p("SSTX HASH160 DATA_20 0x%s EQUAL", p2sh), want: true, }, { name: "almost v0 stake gen p2sh -- wrong hash length", script: p("SSGEN HASH160 DATA_21 0x00%s EQUAL", p2sh), want: false, }, { name: "almost v0 stake gen p2sh -- trailing opcode", script: p("SSGEN HASH160 DATA_20 0x%s EQUAL TRUE", p2sh), want: false, }, { name: "v0 stake gen p2sh", script: p("SSGEN HASH160 DATA_20 0x%s EQUAL", p2sh), want: true, }, { name: "almost v0 stake revocation p2sh -- wrong hash length", script: p("SSRTX HASH160 DATA_21 0x00%s EQUAL", p2sh), want: false, }, { name: "almost v0 stake revocation p2sh -- trailing opcode", script: p("SSRTX HASH160 DATA_20 0x%s EQUAL TRUE", p2sh), want: false, }, { name: "v0 stake revocation p2sh", script: p("SSRTX HASH160 DATA_20 0x%s EQUAL", p2sh), want: true, }, { name: "almost v0 stake change p2sh -- wrong hash length", script: p("SSTXCHANGE HASH160 DATA_21 0x00%s EQUAL", p2sh), want: false, }, { name: "almost v0 stake change p2sh -- trailing opcode", script: p("SSTXCHANGE HASH160 DATA_20 0x%s EQUAL TRUE", p2sh), want: false, }, { name: "v0 stake change p2sh", script: p("SSTXCHANGE HASH160 DATA_20 0x%s EQUAL", p2sh), want: true, }, { name: "almost v0 treasury gen p2sh -- wrong hash length", script: p("TGEN HASH160 DATA_21 0x00%s EQUAL", p2sh), isTrsy: true, want: false, }, { name: "almost v0 treasury gen p2sh -- trailing opcode", script: p("TGEN HASH160 DATA_20 0x%s EQUAL TRUE", p2sh), isTrsy: true, want: false, }, { name: "v0 treasury gen p2sh", script: p("TGEN HASH160 DATA_20 0x%s EQUAL", p2sh), isTrsy: true, want: true, }} for _, test := range tests { // Run the tests with and without treasury enabled. for _, flags := range []ScriptFlags{0, ScriptVerifyTreasury} { vm := Engine{flags: flags} got := vm.isAnyKindOfScriptHash(test.script) want := test.want && (!test.isTrsy || flags != 0) if got != want { t.Errorf("%q: unexpected result -- got %v, want %v", test.name, got, want) } } } } // TestHasCanonicalPushes ensures the isCanonicalPush function properly // determines what is considered a canonical push for the purposes of // removeOpcodeByData and script null data checks. func TestHasCanonicalPushes(t *testing.T) { t.Parallel() const scriptVersion = 0 type canonicalPushTest struct { name string // test description script string // short form script to test expected bool // expected result } tests := []canonicalPushTest{{ name: "does not parse", script: "0x046708afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e" + "0ea1f61d", expected: false, }, { name: "non-canonical push", script: "PUSHDATA1 0x04 0x01020304", expected: false, }} for i := 0; i < 65535; i++ { tests = append(tests, canonicalPushTest{ name: fmt.Sprintf("canonical push of integer %d", i), script: fmt.Sprintf("%d", i), expected: true, }) } for i := 0; i <= MaxScriptElementSize; i++ { tests = append(tests, canonicalPushTest{ name: fmt.Sprintf("canonical push of %d bytes of data", i), script: fmt.Sprintf("'a'{%d}", i), expected: true, }) } for _, test := range tests { script := mustParseShortForm(scriptVersion, test.script) if err := checkScriptParses(scriptVersion, script); err != nil { if test.expected { t.Errorf("%s: script parse failed: %v", test.name, err) } continue } tokenizer := MakeScriptTokenizer(scriptVersion, script) for tokenizer.Next() { result := isCanonicalPush(tokenizer.Opcode(), tokenizer.Data()) if result != test.expected { t.Errorf("%s: wrong result -- got %v, want: %v", test.name, result, test.expected) break } } } } // TestIsPushOnlyScript ensures the IsPushOnlyScript function returns the // expected results. func TestIsPushOnlyScript(t *testing.T) { t.Parallel() type pushOnlyTest struct { name string // test description script string // short form script to test expected bool // expected result } tests := []pushOnlyTest{{ name: "does not parse", script: "0x046708afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0" + "ea1f61d", expected: false, }} for i := 0; i < 65535; i++ { tests = append(tests, pushOnlyTest{ name: fmt.Sprintf("canonical push of integer %d", i), script: fmt.Sprintf("%d", i), expected: true, }) } for i := 0; i <= MaxScriptElementSize; i++ { tests = append(tests, pushOnlyTest{ name: fmt.Sprintf("canonical push of %d bytes of data", i), script: fmt.Sprintf("'a'{%d}", i), expected: true, }) } for _, test := range tests { script := mustParseShortFormV0(test.script) result := IsPushOnlyScript(script) if result != test.expected { t.Errorf("%s: wrong result -- got: %v, want: %v", test.name, result, test.expected) } } } // TestIsUnspendable ensures the IsUnspendable function returns the expected // results. func TestIsUnspendable(t *testing.T) { t.Parallel() tests := []struct { name string amount int64 pkScript string expected bool }{{ name: "unspendable due to being provably pruneable", amount: 100, pkScript: "RETURN DATA_4 0x74657374", expected: true, }, { name: "unspendable due to zero amount", amount: 0, pkScript: "DUP HASH160 DATA_20 0x2995a0fe6843fa9b954597f0dca7a44df6fa" + "0b5c EQUALVERIFY CHECKSIG", expected: true, }, { name: "spendable", amount: 100, pkScript: "DUP HASH160 DATA_20 0x2995a0fe6843fa9b954597f0dca7a44df6fa" + "0b5c EQUALVERIFY CHECKSIG", expected: false, }} for _, test := range tests { pkScript := mustParseShortFormV0(test.pkScript) result := IsUnspendable(test.amount, pkScript) if result != test.expected { t.Errorf("%s: unexpected result -- got %v, want %v", test.name, result, test.expected) continue } } } // TestGenerateSSGenBlockRef ensures the block reference script for use in stake // vote transactions is generated correctly for various block hashes and // heights. func TestGenerateSSGenBlockRef(t *testing.T) { var tests = []struct { blockHash string height uint32 expected []byte }{{ "0000000000004740ad140c86753f9295e09f9cc81b1bb75d7f5552aeeedb7012", 1000, hexToBytes("6a241270dbeeae52557f5db71b1bc89c9fe095923f75860c14ad40470" + "00000000000e8030000"), }, { "000000000000000033eafc268a67c8d1f02343d7a96cf3fe2a4915ef779b52f9", 290000, hexToBytes("6a24f9529b77ef15492afef36ca9d74323f0d1c8678a26fcea3300000" + "00000000000d06c0400"), }} for _, test := range tests { h, err := chainhash.NewHashFromStr(test.blockHash) if err != nil { t.Errorf("unexpected err: %v", err) continue } s, err := GenerateSSGenBlockRef(*h, test.height) if err != nil { t.Errorf("unexpected err: %v", err) continue } if !bytes.Equal(s, test.expected) { t.Errorf("unexpected script -- got %x, want %x", s, test.expected) continue } } } // TestGenerateSSGenVotes ensures the expected vote script for use in stake // vote transactions is generated correctly for various vote bits. func TestGenerateSSGenVotes(t *testing.T) { var tests = []struct { votebits uint16 expected []byte }{ {65535, hexToBytes("6a02ffff")}, {256, hexToBytes("6a020001")}, {127, hexToBytes("6a027f00")}, {0, hexToBytes("6a020000")}, } for _, test := range tests { s, err := GenerateSSGenVotes(test.votebits) if err != nil { t.Errorf("unexpected err: %v", err) continue } if !bytes.Equal(s, test.expected) { t.Errorf("unexpected script -- got %x, want %x", s, test.expected) continue } } }