Skip to content

Commit 69c118a

Browse files
committed
WIP: Integrate optimality checker
Still missing parse_for and parse_if
1 parent 902f7a5 commit 69c118a

File tree

4 files changed

+523
-101
lines changed

4 files changed

+523
-101
lines changed

src/generate.rs

Lines changed: 8 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,26 @@ use proc_macro2::{Ident, Span, TokenStream};
44
use quote::{quote, quote_spanned};
55

66
pub fn generate(syntax: Vec<(Syntax, Span)>) -> TokenStream {
7-
let mut tokens = quote!(::bitcoin::blockdata::script::Builder::new());
7+
let mut tokens = quote!(pushable::Builder::new());
88

99
for (item, span) in syntax {
1010
let push = match item {
11-
Syntax::Opcode(opcode) => generate_opcode(opcode, span),
11+
Syntax::Opcode(opcode) =>generate_opcode(opcode, span),
1212
Syntax::Bytes(bytes) => generate_bytes(bytes, span),
1313
Syntax::Int(int) => generate_int(int, span),
14-
Syntax::Escape(expression) => {
15-
let builder = tokens;
16-
tokens = TokenStream::new();
17-
generate_escape(builder, expression, span)
18-
}
14+
Syntax::Escape(expression) => generate_escape(expression, span)
1915
};
2016
tokens.extend(push);
2117
}
2218

23-
tokens.extend(quote!(.into_script()));
19+
tokens.extend(quote!(.0.into_script()));
2420
tokens
2521
}
2622

2723
fn generate_opcode(opcode: Opcode, span: Span) -> TokenStream {
2824
let ident = Ident::new(opcode.to_string().as_ref(), span);
2925
quote_spanned!(span=>
30-
.push_opcode(
31-
::bitcoin::blockdata::opcodes::all::#ident
32-
)
26+
.push_opcode(::bitcoin::blockdata::opcodes::all::#ident)
3327
)
3428
}
3529

@@ -45,19 +39,8 @@ fn generate_int(n: i64, span: Span) -> TokenStream {
4539
quote_spanned!(span=>.push_int(#n))
4640
}
4741

48-
fn generate_escape(builder: TokenStream, expression: TokenStream, span: Span) -> TokenStream {
42+
fn generate_escape(expression: TokenStream, span: Span) -> TokenStream {
4943
quote_spanned!(span=>
50-
(|builder, value| {
51-
#[allow(clippy::all)]
52-
53-
use ::bitcoin::blockdata::script::Builder;
54-
fn push<T: pushable::Pushable>(builder: Builder, value: T) -> Builder {
55-
value.bitcoin_script_push(builder)
56-
}
57-
push(builder, value)
58-
})(
59-
#builder,
60-
#expression
61-
)
62-
)
44+
.push_expression(#expression)
45+
)
6346
}

src/lib.rs

Lines changed: 219 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -136,87 +136,242 @@ pub fn script(tokens: TokenStream) -> TokenStream {
136136
#[proc_macro]
137137
pub fn define_pushable(_: TokenStream) -> TokenStream {
138138
quote!(
139-
pub mod pushable {
140-
141-
use bitcoin::blockdata::opcodes::Opcode;
142-
use bitcoin::blockdata::script::Builder;
143-
use bitcoin::blockdata::script::PushBytesBuf;
144-
use std::convert::TryFrom;
145-
146-
// We split up the bitcoin_script_push function to allow pushing a single u8 value as
147-
// an integer (i64), Vec<u8> as raw data and Vec<T> for any T: Pushable object that is
148-
// not a u8. Otherwise the Vec<u8> and Vec<T: Pushable> definitions conflict.
149-
trait NotU8Pushable {
150-
fn bitcoin_script_push(self, builder: Builder) -> Builder;
151-
}
152-
impl NotU8Pushable for Opcode {
153-
fn bitcoin_script_push(self, builder: Builder) -> Builder {
154-
builder.push_opcode(self)
139+
pub mod pushable {
140+
141+
use bitcoin::blockdata::opcodes::{all::*, Opcode};
142+
use bitcoin::blockdata::script::Builder as BitcoinBuilder;
143+
use bitcoin::blockdata::script::{PushBytesBuf, Script};
144+
use std::convert::TryFrom;
145+
146+
pub struct Builder(pub BitcoinBuilder);
147+
148+
pub fn check_optimality(opcode: Opcode, next_opcode: Opcode) {
149+
match (opcode, next_opcode) {
150+
(OP_PUSHNUM_1, OP_ADD) => eprintln!("Script can be optimized: 1 OP_ADD => OP_1ADD"),
151+
(OP_PUSHNUM_1, OP_SUB) => eprintln!("Script can be optimized: 1 OP_SUB => OP_1SUB"),
152+
(OP_DROP, OP_DROP) => {
153+
eprintln!("Script can be optimized: OP_DROP OP_DROP => OP_2DROP")
155154
}
156-
}
157-
impl NotU8Pushable for i64 {
158-
fn bitcoin_script_push(self, builder: Builder) -> Builder {
159-
builder.push_int(self)
155+
(OP_PUSHBYTES_0, OP_ROLL) => eprintln!("Script can be optimized: Remove 0 OP_ROLL"),
156+
(OP_PUSHNUM_1, OP_ROLL) => {
157+
eprintln!("Script can be optimized: 1 OP_ROLL => OP_SWAP")
160158
}
161-
}
162-
impl NotU8Pushable for i32 {
163-
fn bitcoin_script_push(self, builder: Builder) -> Builder {
164-
builder.push_int(self as i64)
159+
(OP_PUSHNUM_2, OP_ROLL) => {
160+
eprintln!("Script can be optimized: 2 OP_ROLL => OP_ROT")
165161
}
166-
}
167-
impl NotU8Pushable for u32 {
168-
fn bitcoin_script_push(self, builder: Builder) -> Builder {
169-
builder.push_int(self as i64)
162+
(OP_PUSHBYTES_0, OP_PICK) => {
163+
eprintln!("Script can be optimized: 0 OP_PICK => OP_DUP")
170164
}
171-
}
172-
impl NotU8Pushable for usize {
173-
fn bitcoin_script_push(self, builder: Builder) -> Builder {
174-
builder.push_int(
175-
i64::try_from(self).unwrap_or_else(|_| panic!("Usize does not fit in i64")),
176-
)
165+
(OP_PUSHBYTES_1, OP_PICK) => {
166+
eprintln!("Script can be optimized: 1 OP_PICK => OP_OVER")
177167
}
168+
(OP_IF, OP_ELSE) => eprintln!("Script can be optimized: OP_IF OP_ELSE => OP_NOTIF"),
169+
(_, _) => (),
178170
}
179-
impl NotU8Pushable for Vec<u8> {
180-
fn bitcoin_script_push(self, builder: Builder) -> Builder {
181-
builder.push_slice(PushBytesBuf::try_from(self).unwrap())
182-
}
171+
}
172+
173+
impl Builder {
174+
pub fn new() -> Self {
175+
let builder = BitcoinBuilder::new();
176+
Builder(builder)
183177
}
184-
impl NotU8Pushable for ::bitcoin::PublicKey {
185-
fn bitcoin_script_push(self, builder: Builder) -> Builder {
186-
builder.push_key(&self)
187-
}
178+
179+
pub fn as_bytes(&self) -> &[u8] {
180+
self.0.as_bytes()
188181
}
189-
impl NotU8Pushable for ::bitcoin::ScriptBuf {
190-
fn bitcoin_script_push(self, builder: Builder) -> Builder {
191-
let mut script_vec = vec![];
192-
script_vec.extend_from_slice(builder.as_bytes());
193-
script_vec.extend_from_slice(self.as_bytes());
194-
Builder::from(script_vec)
195-
}
182+
183+
pub fn as_script(&self) -> &Script {
184+
self.0.as_script()
196185
}
197-
impl<T: NotU8Pushable> NotU8Pushable for Vec<T> {
198-
fn bitcoin_script_push(self, mut builder: Builder) -> Builder {
199-
for pushable in self {
200-
builder = pushable.bitcoin_script_push(builder);
201-
}
202-
builder
203-
}
186+
187+
pub fn push_opcode(mut self, opcode: Opcode) -> Builder {
188+
match self.as_script().instructions_minimal().last() {
189+
Some(instr_result) => match instr_result {
190+
Ok(instr) => match instr {
191+
bitcoin::script::Instruction::PushBytes(push_bytes) => {
192+
if push_bytes.as_bytes() == [] {
193+
check_optimality(::bitcoin::opcodes::all::OP_PUSHBYTES_0, opcode)
194+
}
195+
},
196+
bitcoin::script::Instruction::Op(previous_opcode) => {
197+
check_optimality(previous_opcode, opcode)
198+
}
199+
},
200+
Err(_) => eprintln!("Script includes non-minimal pushes."),
201+
},
202+
None => (),
203+
};
204+
self.0 = self.0.push_opcode(opcode);
205+
self
206+
}
207+
208+
pub fn push_int(mut self, int: i64) -> Builder {
209+
self.0 = self.0.push_int(int);
210+
self
204211
}
205-
pub trait Pushable {
206-
fn bitcoin_script_push(self, builder: Builder) -> Builder;
212+
213+
pub fn push_slice(mut self, slice: PushBytesBuf) -> Builder {
214+
self.0 = self.0.push_slice(slice);
215+
self
216+
}
217+
218+
pub fn push_key(mut self, pub_key: &::bitcoin::PublicKey) -> Builder {
219+
self.0 = self.0.push_key(pub_key);
220+
self
207221
}
208-
impl<T: NotU8Pushable> Pushable for T {
209-
fn bitcoin_script_push(self, builder: Builder) -> Builder {
210-
NotU8Pushable::bitcoin_script_push(self, builder)
222+
223+
pub fn push_expression<T: Pushable>(self, expression: T) -> Builder {
224+
let last_opcode_index = match self.as_script().instruction_indices_minimal().last()
225+
{
226+
Some(instr_result) => match instr_result {
227+
Ok((index, instr)) => match instr {
228+
bitcoin::script::Instruction::PushBytes(push_bytes) => {
229+
// Seperately handle OP_0 because it is turned into a PushBytes
230+
// struct in the Script instruction
231+
if push_bytes.as_bytes() == [] {
232+
Some((index, ::bitcoin::opcodes::all::OP_PUSHBYTES_0))
233+
} else {
234+
None
235+
}
236+
},
237+
bitcoin::script::Instruction::Op(opcode) => Some((index, opcode)),
238+
},
239+
Err(_) => {
240+
eprintln!("Script includes non-minimal pushes.");
241+
None
242+
}
243+
},
244+
None => None,
245+
};
246+
let builder = expression.bitcoin_script_push(self);
247+
if let Some((last_index, previous_opcode)) = last_opcode_index {
248+
match builder
249+
.as_script()
250+
.instructions_minimal()
251+
.skip(last_index + 1)
252+
.next()
253+
{
254+
Some(instr_result) => match instr_result {
255+
Ok(instr) => match instr {
256+
bitcoin::script::Instruction::PushBytes(_) => (),
257+
bitcoin::script::Instruction::Op(opcode) => {
258+
check_optimality(previous_opcode, opcode)
259+
}
260+
},
261+
Err(_) => eprintln!("Script includes non-minimal pushes."),
262+
},
263+
None => eprintln!("Script extends an empty script!"),
264+
};
211265
}
266+
builder
212267
}
268+
}
213269

214-
impl Pushable for u8 {
215-
fn bitcoin_script_push(self, builder: Builder) -> Builder {
216-
builder.push_int(self as i64)
270+
impl From<Vec<u8>> for Builder {
271+
fn from(v: Vec<u8>) -> Builder {
272+
let builder = BitcoinBuilder::from(v);
273+
Builder(builder)
274+
}
275+
}
276+
// We split up the bitcoin_script_push function to allow pushing a single u8 value as
277+
// an integer (i64), Vec<u8> as raw data and Vec<T> for any T: Pushable object that is
278+
// not a u8. Otherwise the Vec<u8> and Vec<T: Pushable> definitions conflict.
279+
trait NotU8Pushable {
280+
fn bitcoin_script_push(self, builder: Builder) -> Builder;
281+
}
282+
impl NotU8Pushable for i64 {
283+
fn bitcoin_script_push(self, builder: Builder) -> Builder {
284+
builder.push_int(self)
285+
}
286+
}
287+
impl NotU8Pushable for i32 {
288+
fn bitcoin_script_push(self, builder: Builder) -> Builder {
289+
builder.push_int(self as i64)
290+
}
291+
}
292+
impl NotU8Pushable for u32 {
293+
fn bitcoin_script_push(self, builder: Builder) -> Builder {
294+
builder.push_int(self as i64)
295+
}
296+
}
297+
impl NotU8Pushable for usize {
298+
fn bitcoin_script_push(self, builder: Builder) -> Builder {
299+
builder.push_int(
300+
i64::try_from(self).unwrap_or_else(|_| panic!("Usize does not fit in i64")),
301+
)
302+
}
303+
}
304+
impl NotU8Pushable for Vec<u8> {
305+
fn bitcoin_script_push(self, builder: Builder) -> Builder {
306+
builder.push_slice(PushBytesBuf::try_from(self).unwrap())
307+
}
308+
}
309+
impl NotU8Pushable for ::bitcoin::PublicKey {
310+
fn bitcoin_script_push(self, builder: Builder) -> Builder {
311+
builder.push_key(&self)
312+
}
313+
}
314+
impl NotU8Pushable for ::bitcoin::ScriptBuf {
315+
fn bitcoin_script_push(self, builder: Builder) -> Builder {
316+
let previous_opcode = match self.as_script().instructions_minimal().last() {
317+
Some(instr_result) => match instr_result {
318+
Ok(instr) => match instr {
319+
bitcoin::script::Instruction::PushBytes(_) => None,
320+
bitcoin::script::Instruction::Op(previous_opcode) => {
321+
Some(previous_opcode)
322+
}
323+
},
324+
Err(_) => {
325+
eprintln!("Script includes non-minimal pushes.");
326+
None
327+
}
328+
},
329+
None => None,
330+
};
331+
332+
if let Some(previous_opcode) = previous_opcode {
333+
match self.as_script().instructions_minimal().last() {
334+
Some(instr_result) => match instr_result {
335+
Ok(instr) => match instr {
336+
bitcoin::script::Instruction::PushBytes(_) => (),
337+
bitcoin::script::Instruction::Op(opcode) => {
338+
check_optimality(previous_opcode, opcode)
339+
}
340+
},
341+
Err(_) => eprintln!("Script includes non-minimal pushes."),
342+
},
343+
None => (),
344+
}
345+
};
346+
let mut script_vec = vec![];
347+
script_vec.extend_from_slice(builder.as_bytes());
348+
script_vec.extend_from_slice(self.as_bytes());
349+
Builder::from(script_vec)
350+
}
351+
}
352+
impl<T: NotU8Pushable> NotU8Pushable for Vec<T> {
353+
fn bitcoin_script_push(self, mut builder: Builder) -> Builder {
354+
for pushable in self {
355+
builder = pushable.bitcoin_script_push(builder);
217356
}
357+
builder
358+
}
359+
}
360+
pub trait Pushable {
361+
fn bitcoin_script_push(self, builder: Builder) -> Builder;
362+
}
363+
impl<T: NotU8Pushable> Pushable for T {
364+
fn bitcoin_script_push(self, builder: Builder) -> Builder {
365+
NotU8Pushable::bitcoin_script_push(self, builder)
366+
}
367+
}
368+
369+
impl Pushable for u8 {
370+
fn bitcoin_script_push(self, builder: Builder) -> Builder {
371+
builder.push_int(self as i64)
218372
}
219373
}
374+
}
220375
)
221376
.into()
222377
}

src/parse.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -151,10 +151,21 @@ where
151151
Group(block) if block.delimiter() == Delimiter::Brace => {
152152
let inner_block = block.stream();
153153
escape.extend(quote! {
154-
{
155-
script_var.extend_from_slice(script !{
154+
{
155+
let next_script = script !{
156156
#inner_block
157-
}.as_bytes());
157+
};
158+
if script_var.len() > 0 {
159+
if next_script.as_bytes().len() == 0 {
160+
eprintln!("Script can be optimized: Inner block of a for loop iteration is empty.");
161+
} else {
162+
// TODO this could be a data push
163+
let previous_opcode = ::bitcoin::opcodes::Opcode::from(script_var[script_var.len() - 1]);
164+
let opcode = ::bitcoin::opcodes::Opcode::from(next_script.as_bytes()[0]);
165+
pushable::check_optimality(previous_opcode, opcode);
166+
}
167+
}
168+
script_var.extend_from_slice(next_script.as_bytes());
158169
}
159170
bitcoin::script::ScriptBuf::from(script_var)
160171
});

0 commit comments

Comments
 (0)