Completed
Push — master ( 3be9f6...d78503 )
by thomas
28:30
created

ScriptCreator   A

Complexity

Total Complexity 24

Size/Duplication

Total Lines 187
Duplicated Lines 0 %

Test Coverage

Coverage 88.33%

Importance

Changes 0
Metric Value
eloc 58
dl 0
loc 187
ccs 53
cts 60
cp 0.8833
rs 10
c 0
b 0
f 0
wmc 24

9 Methods

Rating   Name   Duplication   Size   Complexity  
A getScript() 0 3 1
A int() 0 11 5
A data() 0 4 1
A concat() 0 4 1
A push() 0 23 4
A __construct() 0 8 2
A op() 0 8 2
A opcode() 0 4 1
B sequence() 0 23 7
1
<?php
2
3
declare(strict_types=1);
4
5
namespace BitWasp\Bitcoin\Script\Factory;
6
7
use BitWasp\Bitcoin\Math\Math;
8
use BitWasp\Bitcoin\Script\Interpreter\Number;
9
use BitWasp\Bitcoin\Script\Opcodes;
10
use BitWasp\Bitcoin\Script\Script;
11
use BitWasp\Bitcoin\Script\ScriptInterface;
12
use BitWasp\Buffertools\Buffer;
13
use BitWasp\Buffertools\BufferInterface;
14
15
class ScriptCreator
16
{
17
    /**
18
     * @var string
19
     */
20
    private $script = '';
21
22
    /**
23
     * @var Opcodes
24
     */
25
    private $opcodes;
26
27
    /**
28
     * @var Math
29
     */
30
    private $math;
31
32
    /**
33
     * @param Math $math
34
     * @param Opcodes $opcodes
35
     * @param BufferInterface|null $buffer
36
     */
37 5382
    public function __construct(Math $math, Opcodes $opcodes, BufferInterface $buffer = null)
38
    {
39 5382
        if ($buffer !== null) {
40 23
            $this->script = $buffer->getBinary();
41
        }
42
43 5382
        $this->math = $math;
44 5382
        $this->opcodes = $opcodes;
45 5382
    }
46
47
    /**
48
     * Add a data-push instruction to the script,
49
     * pushing x bytes of $data from $data, with
50
     * the appropriate marker for the different
51
     * PUSHDATA opcodes.
52
     *
53
     * @param BufferInterface $data
54
     * @return $this
55
     */
56 398
    public function push(BufferInterface $data)
57
    {
58 398
        $length = $data->getSize();
59
60 398
        if ($length < Opcodes::OP_PUSHDATA1) {
61 395
            $this->script .= pack('C', $length) . $data->getBinary();
62
        } else {
63 11
            if ($length <= 0xff) {
64 9
                $lengthSize = 1;
65 9
                $code = 'C';
66 2
            } elseif ($length <= 0xffff) {
67 1
                $lengthSize = 2;
68 1
                $code = 'S';
69
            } else {
70 1
                $lengthSize = 4;
71 1
                $code = 'V';
72
            }
73
74 11
            $opCode = constant("BitWasp\\Bitcoin\\Script\\Opcodes::OP_PUSHDATA" . $lengthSize);
75 11
            $this->script .= pack('C', $opCode) . pack($code, $length) . $data->getBinary();
76
        }
77
78 398
        return $this;
79
    }
80
81
    /**
82
     * Concatenate $script onto $this.
83
     * @param ScriptInterface $script
84
     * @return $this
85
     */
86 5365
    public function concat(ScriptInterface $script)
87
    {
88 5365
        $this->script .= $script->getBinary();
89 5365
        return $this;
90
    }
91
92
    /**
93
     * This function accepts an array of elements, builds
94
     * an intermediate script composed of the items in $sequence,
95
     * and concatenates it in one step.
96
     *
97
     * The allowed types are:
98
     *  - opcode (integer form)
99
     *  - script number (Number class)
100
     *  - data (BufferInterface)
101
     *  - script (ScriptInterface)
102
     *
103
     * @param int[]|\BitWasp\Bitcoin\Script\Interpreter\Number[]|BufferInterface[] $sequence
104
     * @return $this
105
     */
106 5365
    public function sequence(array $sequence)
107
    {
108 5365
        $new = new self($this->math, $this->opcodes, null);
109 5365
        foreach ($sequence as $operation) {
110 5364
            if (is_int($operation)) {
111 5342
                if (!$this->opcodes->offsetExists($operation)) {
112
                    throw new \RuntimeException('Unknown opcode');
113
                }
114
115 5342
                $new->script .= chr($operation);
116 377
            } elseif ($operation instanceof Number) {
117
                $new->push($operation->getBuffer());
118 377
            } elseif ($operation instanceof BufferInterface) {
119 377
                $new->push($operation);
120
            } elseif ($operation instanceof ScriptInterface) {
121
                $new->concat($operation);
122
            } else {
123
                throw new \RuntimeException('Value must be an opcode/BufferInterface/Number');
124
            }
125
        }
126
127 5365
        $this->concat($new->getScript());
128 5365
        return $this;
129
    }
130
131
    /**
132
     * This function accepts an integer, and adds the appropriate
133
     * data-push instruction to the script, minimally encoding it
134
     * where possible.
135
     *
136
     * @param int $n
137
     * @return $this
138
     */
139 42
    public function int(int $n)
140
    {
141 42
        if ($n === 0) {
142
            $this->script .= chr(Opcodes::OP_0);
143 42
        } else if ($n === -1 || ($n >= 1 && $n <= 16)) {
144 42
            $this->script .= chr(\BitWasp\Bitcoin\Script\encodeOpN($n));
145
        } else {
146
            $this->push(Number::int($n)->getBuffer());
147
        }
148
149 42
        return $this;
150
    }
151
152
    /**
153
     * Takes a list of opcodes (the name as a string)
154
     * and adds the opcodes to the script.
155
     *
156
     * @param string... $opNames
0 ignored issues
show
Documentation Bug introduced by
The doc comment string... at position 0 could not be parsed: Unknown type name 'string...' at position 0 in string....
Loading history...
157
     * @return $this
158
     */
159 2
    public function op(string... $opNames)
160
    {
161 2
        $opCodes = [];
162 2
        foreach ($opNames as $opName) {
163 2
            $opCodes[] = $this->opcodes->getOpByName($opName);
164
        }
165
166 1
        return $this->sequence($opCodes);
167
    }
168
169
    /**
170
     * Takes a list of opcodes (in integer form) and
171
     * adds them to the script.
172
     *
173
     * @param int ...$opcodes
174
     * @return $this
175
     */
176 54
    public function opcode(int ...$opcodes)
177
    {
178 54
        $this->sequence($opcodes);
179 54
        return $this;
180
    }
181
182
    /**
183
     * Takes a list of data elements and adds the
184
     * push-data instructions to the script.
185
     *
186
     * @param BufferInterface ...$dataList
187
     * @return $this
188
     */
189 3
    public function data(BufferInterface ...$dataList)
190
    {
191 3
        $this->sequence($dataList);
192 3
        return $this;
193
    }
194
195
    /**
196
     * Generates a script based on the current state.
197
     * @return ScriptInterface
198
     */
199 5381
    public function getScript(): ScriptInterface
200
    {
201 5381
        return new Script(new Buffer($this->script), $this->opcodes);
202
    }
203
}
204