diff --git a/bscript/interpreter/opcodeparser.go b/bscript/interpreter/opcodeparser.go index 02124a61..8e3f8092 100644 --- a/bscript/interpreter/opcodeparser.go +++ b/bscript/interpreter/opcodeparser.go @@ -136,6 +136,7 @@ func (o *ParsedOpcode) enforceMinimumDataPush() error { func (p *DefaultOpcodeParser) Parse(s *bscript.Script) (ParsedScript, error) { script := *s parsedOps := make([]ParsedOpcode, 0, len(script)) + conditionalBlock := 0 for i := 0; i < len(script); { instruction := script[i] @@ -145,6 +146,22 @@ func (p *DefaultOpcodeParser) Parse(s *bscript.Script) (ParsedScript, error) { return nil, errs.NewError(errs.ErrInvalidParams, "tx and previous output must be supplied for checksig") } + switch parsedOp.op.val { + case bscript.OpIF, bscript.OpNOTIF, bscript.OpVERIF, bscript.OpVERNOTIF: + conditionalBlock++ + case bscript.OpENDIF: + conditionalBlock-- + case bscript.OpRETURN: + // If we are not in a conditional block, we end script evaluation. + // This must be the final evaluated opcode, everything after is ignored. + if conditionalBlock == 0 { + parsedOps = append(parsedOps, parsedOp) + return parsedOps, nil + } + // If we are in an conditional block, we continue parsing the other branches, + // therefore all data must adhere to push data rules. + } + switch { case parsedOp.op.length == 1: i++ diff --git a/bscript/interpreter/opcodeparser_test.go b/bscript/interpreter/opcodeparser_test.go index 13460c7b..d8951121 100644 --- a/bscript/interpreter/opcodeparser_test.go +++ b/bscript/interpreter/opcodeparser_test.go @@ -9,6 +9,7 @@ import ( "github.com/libsv/go-bt/v2/bscript" "github.com/libsv/go-bt/v2/bscript/interpreter/errs" + "github.com/stretchr/testify/require" ) // TestOpcodeDisabled tests the opcodeDisabled function manually because all @@ -28,3 +29,65 @@ func TestOpcodeDisabled(t *testing.T) { } } } + +func TestParse(t *testing.T) { + tt := []struct { + name string + scriptHexString string + + expectedParsedScript ParsedScript + }{ + { + name: "1 op return", + scriptHexString: "0168776a0024dc", + + expectedParsedScript: ParsedScript{ + ParsedOpcode{ + op: opcode{ + val: bscript.OpDATA1, + name: "OP_DATA_1", + length: 2, + exec: opcodePushData, + }, + Data: []byte{bscript.OpENDIF}, + }, + ParsedOpcode{ + op: opcode{ + val: bscript.OpNIP, + name: "OP_NIP", + length: 1, + exec: opcodeNip, + }, + Data: nil, + }, + ParsedOpcode{ + op: opcode{ + val: bscript.OpRETURN, + name: "OP_RETURN", + length: 1, + exec: opcodeReturn, + }, + Data: nil, + }, + }, + }, + } + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + s, err := bscript.NewFromHexString(tc.scriptHexString) + require.NoError(t, err) + + codeParser := DefaultOpcodeParser{} + p, err := codeParser.Parse(s) + require.NoError(t, err) + + for i := range p { + require.Equal(t, tc.expectedParsedScript[i].Data, p[i].Data) + require.Equal(t, tc.expectedParsedScript[i].op.length, p[i].op.length) + require.Equal(t, tc.expectedParsedScript[i].op.name, p[i].op.name) + require.Equal(t, tc.expectedParsedScript[i].op.val, p[i].op.val) + } + }) + } +}