From d1f4ef87fda8053dbd49b1108a827ffb726be348 Mon Sep 17 00:00:00 2001 From: Gaston Zanitti Date: Fri, 19 Apr 2024 14:14:38 -0300 Subject: [PATCH] Hardfork selection added to documentation --- Cargo.lock | 1 + doc/src/ch02-lang/README.md | 3 +- doc/src/ch02-lang/ch02-expressions.md | 14 +- doc/src/ch02-lang/ch03-labels.md | 6 +- doc/src/ch02-lang/ch04-macros/README.md | 7 +- .../ch02-lang/ch04-macros/ch01-builtins.md | 37 +- .../ch02-lang/ch04-macros/ch02-expression.md | 6 +- .../ch02-lang/ch04-macros/ch03-instruction.md | 6 +- etk-analyze/Cargo.toml | 12 +- etk-analyze/src/cfg.rs | 3 +- etk-asm/src/asm.rs | 455 ++++++++++-------- etk-asm/src/ast.rs | 24 +- etk-asm/src/bin/eas.rs | 13 +- etk-asm/src/ingest.rs | 74 ++- etk-asm/src/ops.rs | 103 +++- etk-asm/src/parse/asm.pest | 13 +- etk-asm/src/parse/error.rs | 93 ++++ etk-asm/src/parse/macros.rs | 123 ++++- etk-asm/src/parse/mod.rs | 126 +++-- etk-asm/tests/asm.rs | 73 ++- .../tests/asm/hardfork/invalid-hardfork.etk | 1 + etk-asm/tests/asm/hardfork/invalid-range.etk | 1 + etk-asm/tests/asm/hardfork/valid-hardfork.etk | 5 + etk-asm/tests/asm/variable-push/included.etk | 2 +- etk-asm/tests/asm/variable-push/main.etk | 2 +- etk-dasm/src/bin/disease/selectors.rs | 3 +- etk-dasm/src/blocks/annotated.rs | 1 + etk-dasm/src/blocks/basic.rs | 3 +- etk-ops/build.rs | 76 +-- 29 files changed, 874 insertions(+), 412 deletions(-) create mode 100644 etk-asm/tests/asm/hardfork/invalid-hardfork.etk create mode 100644 etk-asm/tests/asm/hardfork/invalid-range.etk create mode 100644 etk-asm/tests/asm/hardfork/valid-hardfork.etk diff --git a/Cargo.lock b/Cargo.lock index f4c9309..2e23e01 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -428,6 +428,7 @@ dependencies = [ "etk-asm", "etk-cli", "etk-dasm", + "etk-ops", "hex-literal", "petgraph", "snafu", diff --git a/doc/src/ch02-lang/README.md b/doc/src/ch02-lang/README.md index 6b35f10..9b9e764 100644 --- a/doc/src/ch02-lang/README.md +++ b/doc/src/ch02-lang/README.md @@ -12,6 +12,7 @@ This example should increment a value from 0 to 255 on the stack, then halt exec ```rust # extern crate etk_asm; +# extern crate etk_ops; # let src = r#" push1 0x00 @@ -28,7 +29,7 @@ loop: pop stop # This halts execution # "#; -# let mut ingest = etk_asm::ingest::Ingest::new(Vec::new()); +# let mut ingest = etk_asm::ingest::Ingest::new(Vec::new(), etk_ops::HardFork::Cancun); # ingest.ingest(file!(), src).unwrap(); ``` diff --git a/doc/src/ch02-lang/ch02-expressions.md b/doc/src/ch02-lang/ch02-expressions.md index 456d506..a39e78b 100644 --- a/doc/src/ch02-lang/ch02-expressions.md +++ b/doc/src/ch02-lang/ch02-expressions.md @@ -10,18 +10,19 @@ While an assembled `push` must have a concrete value, it is often useful when de ```rust # extern crate etk_asm; +# extern crate etk_ops; # let src = r#" push1 1+(2*3)/4 # "#; # let mut output = Vec::new(); -# let mut ingest = etk_asm::ingest::Ingest::new(&mut output); +# let mut ingest = etk_asm::ingest::Ingest::new(&mut output, etk_ops::HardFork::Cancun); # ingest.ingest(file!(), src).unwrap(); # assert_eq!(output, &[0x60, 0x02]); ``` ## Definition -An **expression** is a standard infix mathematical expression that is evaluated during assembly. Its computed value *must* fit within the preceding `push`'s size allowance (eg. less than 256 for `push8`). +An **expression** is a standard infix mathematical expression that is evaluated during assembly. Its computed value _must_ fit within the preceding `push`'s size allowance (eg. less than 256 for `push8`). ### Terms @@ -50,12 +51,13 @@ A [label](ch03-labels.md) may be used as a term in an expression. ```rust # extern crate etk_asm; +# extern crate etk_ops; # let src = r#" start: push1 start + 1 # "#; # let mut output = Vec::new(); -# let mut ingest = etk_asm::ingest::Ingest::new(&mut output); +# let mut ingest = etk_asm::ingest::Ingest::new(&mut output, etk_ops::HardFork::Cancun); # ingest.ingest(file!(), src).unwrap(); # assert_eq!(output, &[0x60, 0x01]); ``` @@ -66,11 +68,12 @@ start: ```rust # extern crate etk_asm; +# extern crate etk_ops; # let src = r#" push4 selector("transfer(uint256,uint256)") # "#; # let mut output = Vec::new(); -# let mut ingest = etk_asm::ingest::Ingest::new(&mut output); +# let mut ingest = etk_asm::ingest::Ingest::new(&mut output, etk_ops::HardFork::Cancun); # ingest.ingest(file!(), src).unwrap(); # assert_eq!(output, &[0x63, 12, 247, 158, 10]); ``` @@ -83,6 +86,7 @@ Expressions support the following binary operators: ```rust # extern crate etk_asm; +# extern crate etk_ops; # let src = r#" push1 1+2 # addition push1 1*2 # multiplication @@ -91,7 +95,7 @@ push1 2/2 # division # "#; # let mut output = Vec::new(); -# let mut ingest = etk_asm::ingest::Ingest::new(&mut output); +# let mut ingest = etk_asm::ingest::Ingest::new(&mut output, etk_ops::HardFork::Cancun); # ingest.ingest(file!(), src).unwrap(); # assert_eq!(output, &[0x60, 0x03, 0x60, 0x02, 0x60, 0x01, 0x60, 0x01]); ``` diff --git a/doc/src/ch02-lang/ch03-labels.md b/doc/src/ch02-lang/ch03-labels.md index eb88ff8..6aba639 100644 --- a/doc/src/ch02-lang/ch03-labels.md +++ b/doc/src/ch02-lang/ch03-labels.md @@ -4,6 +4,7 @@ Manually counting out jump destination addresses would be a monumentally pointle ```rust # extern crate etk_asm; +# extern crate etk_ops; # let src = r#" label0: # <- This is a label called "label0", ## and it has the value 0, since it is @@ -16,7 +17,7 @@ label0: # <- This is a label called "label0", jump # Now we jump to zero, which is a ## `jumpdest` instruction, looping forever. # "#; -# let mut ingest = etk_asm::ingest::Ingest::new(Vec::new()); +# let mut ingest = etk_asm::ingest::Ingest::new(Vec::new(), etk_ops::HardFork::Cancun); # ingest.ingest(file!(), src).unwrap(); ``` @@ -34,6 +35,7 @@ That's not all! You can also use labels to calculate lengths: ```rust # extern crate etk_asm; +# extern crate etk_ops; # let src = r#" push1 start push1 end @@ -46,7 +48,7 @@ start: pc end: # "#; -# let mut ingest = etk_asm::ingest::Ingest::new(Vec::new()); +# let mut ingest = etk_asm::ingest::Ingest::new(Vec::new(), etk_ops::HardFork::Cancun); # ingest.ingest(file!(), src).unwrap(); ``` diff --git a/doc/src/ch02-lang/ch04-macros/README.md b/doc/src/ch02-lang/ch04-macros/README.md index 4e19be8..bf41257 100644 --- a/doc/src/ch02-lang/ch04-macros/README.md +++ b/doc/src/ch02-lang/ch04-macros/README.md @@ -12,6 +12,7 @@ An instruction macro looks like this: ```rust # extern crate etk_asm; +# extern crate etk_ops; # let src = r#" %macro push_sum(a, b) push1 $a + $b @@ -20,7 +21,7 @@ An instruction macro looks like this: %push_sum(4, 2) # "#; # let mut output = Vec::new(); -# let mut ingest = etk_asm::ingest::Ingest::new(&mut output); +# let mut ingest = etk_asm::ingest::Ingest::new(&mut output, etk_ops::HardFork::Cancun); # ingest.ingest(file!(), src).unwrap(); # assert_eq!(output, &[0x60, 0x06]); ``` @@ -37,6 +38,7 @@ Expression macros _do not_ begin with `%`, and cannot replace instructions. Inst ```rust # extern crate etk_asm; +# extern crate etk_ops; # let src = r#" %def add_one(num) $num+1 @@ -45,7 +47,7 @@ Expression macros _do not_ begin with `%`, and cannot replace instructions. Inst push1 add_one(41) # "#; # let mut output = Vec::new(); -# let mut ingest = etk_asm::ingest::Ingest::new(&mut output); +# let mut ingest = etk_asm::ingest::Ingest::new(&mut output, etk_ops::HardFork::Cancun); # ingest.ingest(file!(), src).unwrap(); # assert_eq!(output, &[0x60, 0x2a]); ``` @@ -55,4 +57,3 @@ Here, `add_one(...)` is an expression macro that returns the `num+1`. The fully ```ignore push1 42 ``` - diff --git a/doc/src/ch02-lang/ch04-macros/ch01-builtins.md b/doc/src/ch02-lang/ch04-macros/ch01-builtins.md index 05581df..fa5d2dd 100644 --- a/doc/src/ch02-lang/ch04-macros/ch01-builtins.md +++ b/doc/src/ch02-lang/ch04-macros/ch01-builtins.md @@ -39,8 +39,8 @@ stop The `%include` macro expands to the instructions read from another file, but unlike `%import`, the included file is assembled independently from the current file: - - Labels from the included file are _not_ available in the including file, and vise versa. - - The address of the first instruction in the included file will be zero. +- Labels from the included file are _not_ available in the including file, and vise versa. +- The address of the first instruction in the included file will be zero. The path is resolved relative to the current file. @@ -79,6 +79,7 @@ For example: ```rust # extern crate etk_asm; +# extern crate etk_ops; # let src = r#" %push(hello) @@ -86,7 +87,7 @@ hello: jumpdest # "#; # let mut output = Vec::new(); -# let mut ingest = etk_asm::ingest::Ingest::new(&mut output); +# let mut ingest = etk_asm::ingest::Ingest::new(&mut output, etk_ops::HardFork::Cancun); # ingest.ingest(file!(), src).unwrap(); # assert_eq!(output, &[0x60, 0x02, 0x5b]); ``` @@ -108,11 +109,12 @@ For example: ```rust # extern crate etk_asm; +# extern crate etk_ops; # let src = r#" push4 selector("transfer(address,uint256)") # <- expands to 0x63a9059cbb # "#; # let mut output = Vec::new(); -# let mut ingest = etk_asm::ingest::Ingest::new(&mut output); +# let mut ingest = etk_asm::ingest::Ingest::new(&mut output, etk_ops::HardFork::Cancun); # ingest.ingest(file!(), src).unwrap(); # assert_eq!(output, &[0x63, 0xa9, 0x05, 0x9c, 0xbb]); ``` @@ -131,11 +133,12 @@ For example: ```rust # extern crate etk_asm; +# extern crate etk_ops; # let src = r#" push32 topic("transfer(address,uint256)") # "#; # let mut output = Vec::new(); -# let mut ingest = etk_asm::ingest::Ingest::new(&mut output); +# let mut ingest = etk_asm::ingest::Ingest::new(&mut output, etk_ops::HardFork::Cancun); # ingest.ingest(file!(), src).unwrap(); # assert_eq!(output, &[0x7f, 169, 5, 156, 187, 42, 176, 158, 178, 25, 88, 63, 74, 89, 165, 208, 98, 58, 222, 52, 109, 150, 43, 205, 78, 70, 177, 29, 160, 71, 201, 4, 155]); ``` @@ -146,4 +149,28 @@ The fully expanded source would look like: push32 0xa9059cbb2ab09eb219583f4a59a5d0623ade346d962bcd4e46b11da047c9049b ``` +## Hardfork selection + +### `%hardfork(...)` + +The `%hardfork` macro is useful when you want to restrict the hardfork range in which the code can be compiled. + +For example: + +```rust +# extern crate etk_asm; +# extern crate etk_ops; +# let src = r#" +%hardfork(">london,<=cancun") + +%push(0x08) +# "#; +# let mut output = Vec::new(); +# let mut ingest = etk_asm::ingest::Ingest::new(&mut output, etk_ops::HardFork::Cancun); +# ingest.ingest(file!(), src).unwrap(); +# assert_eq!(output, &[0x60, 0x08]); +``` + +can only be compiled by specifying a hardfork using the `--hardfork` flag (with the latest hardfork implemented as the default option) strictly after `London` and equal or older than `Cancun`. Non-closed ranges can also be used, as well as specific versions (e.g. `%hardfork(">london")`, `%hardfork("cancun")` and `%hardfork(">=cancun,<=cancun")` are all valids.`). + [abi]: https://docs.soliditylang.org/en/latest/abi-spec.html#function-selector diff --git a/doc/src/ch02-lang/ch04-macros/ch02-expression.md b/doc/src/ch02-lang/ch04-macros/ch02-expression.md index 8be11f7..ef22138 100644 --- a/doc/src/ch02-lang/ch04-macros/ch02-expression.md +++ b/doc/src/ch02-lang/ch04-macros/ch02-expression.md @@ -8,6 +8,7 @@ Expression macros can accept an arbitrary number of parameters. Parameters are r ```rust # extern crate etk_asm; +# extern crate etk_ops; # let src = r#" %def my_macro() 42 @@ -17,7 +18,7 @@ Expression macros can accept an arbitrary number of parameters. Parameters are r $x+$y+$z %end # "#; -# let mut ingest = etk_asm::ingest::Ingest::new(Vec::new()); +# let mut ingest = etk_asm::ingest::Ingest::new(Vec::new(), etk_ops::HardFork::Cancun); # ingest.ingest(file!(), src).unwrap(); ``` @@ -27,6 +28,7 @@ Expression macros can be invoked anywhere an expression is expected. ```rust # extern crate etk_asm; +# extern crate etk_ops; # let src = r#" # %def my_macro() # 42 @@ -38,7 +40,7 @@ push1 my_macro() push1 sum(1, 2, my_macro()) # "#; # let mut output = Vec::new(); -# let mut ingest = etk_asm::ingest::Ingest::new(&mut output); +# let mut ingest = etk_asm::ingest::Ingest::new(&mut output, etk_ops::HardFork::Cancun); # ingest.ingest(file!(), src).unwrap(); # assert_eq!(output, &[0x60, 0x2a, 0x60, 0x2d]); ``` diff --git a/doc/src/ch02-lang/ch04-macros/ch03-instruction.md b/doc/src/ch02-lang/ch04-macros/ch03-instruction.md index 07dde03..9c29e04 100644 --- a/doc/src/ch02-lang/ch04-macros/ch03-instruction.md +++ b/doc/src/ch02-lang/ch04-macros/ch03-instruction.md @@ -8,6 +8,7 @@ Instruction macros can accept an arbitrary number of parameters. Parameters are ```rust # extern crate etk_asm; +# extern crate etk_ops; # let src = r#" %macro my_macro() push1 42 @@ -17,7 +18,7 @@ Instruction macros can accept an arbitrary number of parameters. Parameters are push1 $x+$y+$z %end # "#; -# let mut ingest = etk_asm::ingest::Ingest::new(Vec::new()); +# let mut ingest = etk_asm::ingest::Ingest::new(Vec::new(), etk_ops::HardFork::Cancun); # ingest.ingest(file!(), src).unwrap(); ``` @@ -27,6 +28,7 @@ Expression macros can be invoked anywhere an instruction is expected. ```rust # extern crate etk_asm; +# extern crate etk_ops; # let src = r#" # %macro my_macro() # push1 42 @@ -38,7 +40,7 @@ Expression macros can be invoked anywhere an instruction is expected. %sum(1, 2, 3) # "#; # let mut output = Vec::new(); -# let mut ingest = etk_asm::ingest::Ingest::new(&mut output); +# let mut ingest = etk_asm::ingest::Ingest::new(&mut output, etk_ops::HardFork::Cancun); # ingest.ingest(file!(), src).unwrap(); # assert_eq!(output, &[0x60, 0x2a, 0x60, 0x06]); ``` diff --git a/etk-analyze/Cargo.toml b/etk-analyze/Cargo.toml index 886c1f3..3450470 100644 --- a/etk-analyze/Cargo.toml +++ b/etk-analyze/Cargo.toml @@ -2,14 +2,21 @@ name = "etk-analyze" version = "0.4.0-dev" edition = "2018" -authors = ["Sam Wilson ", "lightclient "] +authors = [ + "Sam Wilson ", + "lightclient ", +] license = "MIT OR Apache-2.0" description = "EVM Toolkit analysis tools" homepage = "https://quilt.github.io/etk" repository = "https://github.com/quilt/etk" readme = "README.md" keywords = ["etk", "ethereum"] -categories = ["cryptography::cryptocurrencies", "command-line-utilities", "development-tools"] +categories = [ + "cryptography::cryptocurrencies", + "command-line-utilities", + "development-tools", +] [features] cli = ["etk-cli", "etk-asm", "clap", "snafu"] @@ -20,6 +27,7 @@ clap = { optional = true, version = "3.1", features = ["derive"] } etk-cli = { optional = true, path = "../etk-cli", version = "0.4.0-dev" } etk-asm = { optional = true, path = "../etk-asm", version = "0.4.0-dev" } etk-dasm = { path = "../etk-dasm", version = "0.4.0-dev" } +etk-ops = { path = "../etk-ops", version = "0.4.0-dev" } z3 = { version = "0.11.2", features = ["static-link-z3"] } [dependencies.petgraph] diff --git a/etk-analyze/src/cfg.rs b/etk-analyze/src/cfg.rs index d52cbb8..82bf9f3 100644 --- a/etk-analyze/src/cfg.rs +++ b/etk-analyze/src/cfg.rs @@ -295,6 +295,7 @@ mod tests { use etk_asm::disasm::Disassembler; use etk_asm::ingest::Ingest; + use etk_ops::HardFork; use etk_dasm::blocks::basic::Separator; @@ -328,7 +329,7 @@ mod tests { { fn compile(&self) -> Disassembler { let mut output = Disassembler::new(); - Ingest::new(&mut output) + Ingest::new(&mut output, HardFork::Cancun) .ingest("./test", self.source) .unwrap(); output diff --git a/etk-asm/src/asm.rs b/etk-asm/src/asm.rs index 622308e..b23777d 100644 --- a/etk-asm/src/asm.rs +++ b/etk-asm/src/asm.rs @@ -6,7 +6,7 @@ mod error { use crate::ops::Expression; use crate::ParseError; - use etk_ops::cancun::Op; + use etk_ops::HardForkOp; use num_bigint::BigInt; use snafu::{Backtrace, Snafu}; @@ -53,7 +53,7 @@ mod error { value: BigInt, /// The specifier. - spec: Op<()>, + spec: HardForkOp<()>, /// The location of the error. backtrace: Backtrace, @@ -142,9 +142,11 @@ mod error { pub use self::error::Error; use crate::ops::expression::Error::{UndefinedVariable, UnknownLabel, UnknownMacro}; -use crate::ops::{self, AbstractOp, Assemble, Expression, MacroDefinition}; +use crate::ops::{self, AbstractOp, Assemble, Expression, Imm, MacroDefinition}; +use etk_ops::HardFork; use indexmap::IndexMap; use num_bigint::BigInt; + use rand::Rng; use std::collections::{hash_map, HashMap, HashSet}; @@ -190,11 +192,12 @@ impl From<&AbstractOp> for RawOp { /// use etk_asm::asm::Assembler; /// use etk_asm::ops::AbstractOp; /// use etk_ops::cancun::{Op, GetPc}; +/// use etk_ops::{HardFork, HardForkOp}; /// # use etk_asm::asm::Error; /// # /// # use hex_literal::hex; -/// let mut asm = Assembler::new(); -/// let code = vec![AbstractOp::new(GetPc)]; +/// let mut asm = Assembler::new_with_hardfork(HardFork::Cancun); +/// let code = vec![AbstractOp::new(HardForkOp::Cancun(GetPc.into()))]; /// let result = asm.assemble(&code)?; /// # assert_eq!(result, hex!("58")); /// # Result::<(), Error>::Ok(()) @@ -218,23 +221,22 @@ pub struct Assembler { undeclared_labels: HashSet, /// Pushes that are variable-sized and need to be backpatched. - variable_sized_push: Vec, + variable_sized_push: Vec, + + /// Hardfork to use when assembling. + hardfork: HardFork, } /// A label definition. #[derive(Clone, Copy, Debug, PartialEq)] pub struct LabelDef { position: usize, - updated: bool, } impl LabelDef { /// Create a new `LabelDef`. pub fn new(position: usize) -> Self { - Self { - position, - updated: false, - } + Self { position } } /// Get the position of the label. @@ -243,12 +245,27 @@ impl LabelDef { } } +/// A variable push +#[derive(Debug, Clone)] +pub struct VariablePushDef { + position: usize, + imm: Imm, +} + impl Assembler { - /// Create a new `Assembler`. + /// Create a new `Assembler` for the last hardfork. pub fn new() -> Self { Self::default() } + /// Create a new `Assembler` for an specific hardfork. + pub fn new_with_hardfork(hardfork: HardFork) -> Self { + Self { + hardfork, + ..Self::default() + } + } + /// Feed instructions into the `Assembler`. /// /// Returns the code of the assembled program. @@ -309,7 +326,6 @@ impl Assembler { label, Some(LabelDef { position: self.concrete_len, - updated: false, }), ) .expect("label should exist"); @@ -320,10 +336,10 @@ impl Assembler { self.expand_macro(&m.name, &m.parameters)?; } RawOp::Op(ref op) => { - match op - .clone() - .concretize((&self.declared_labels, &self.declared_macros).into()) - { + match op.clone().concretize( + (&self.declared_labels, &self.declared_macros).into(), + self.hardfork.clone(), + ) { Ok(cop) => { self.concrete_len += cop.size(); self.ready.push(rop.clone()) @@ -354,11 +370,14 @@ impl Assembler { .into_iter() .collect::>(); - if let AbstractOp::Push(_) = op { + if let AbstractOp::Push(imm) = op { // Here, we set the size of the push to 2 bytes (min possible value), // as we don't know the final value of the label yet. self.concrete_len += 2; - self.variable_sized_push.push(op.clone()); + self.variable_sized_push.push(VariablePushDef { + position: self.concrete_len, + imm: imm.clone(), + }); } else { self.concrete_len += op.size().unwrap(); } @@ -391,24 +410,25 @@ impl Assembler { fn backpatch_labels(&mut self) -> Result<(), Error> { for op in self.variable_sized_push.iter() { - if let AbstractOp::Push(imm) = op { - let exp = imm - .tree - .eval_with_context((&self.declared_labels, &self.declared_macros).into()); - - if let Ok(val) = exp { - let val_bits = BigInt::bits(&val).max(1); - let imm_size = 1 + ((val_bits - 1) / 8); - - if imm_size > 1 { - for label_value in self.declared_labels.values_mut() { - let labeldef = label_value.as_ref().unwrap(); - self.concrete_len += imm_size as usize - 1; - *label_value = Some(LabelDef { - position: labeldef.position + imm_size as usize - 1, - updated: true, - }); + let exp = op + .imm + .tree + .eval_with_context((&self.declared_labels, &self.declared_macros).into()); + + if let Ok(val) = exp { + let val_bits = BigInt::bits(&val).max(1); + let imm_size = 1 + ((val_bits - 1) / 8); + + if imm_size > 1 { + for label_value in self.declared_labels.values_mut() { + let labeldef = label_value.as_ref().unwrap(); + if op.position > labeldef.position { + continue; } + self.concrete_len += imm_size as usize - 1; + *label_value = Some(LabelDef { + position: labeldef.position + imm_size as usize - 1, + }); } } } @@ -462,10 +482,10 @@ impl Assembler { RawOp::Scope(_) => unreachable!("scopes should be expanded"), }; - match op - .clone() - .concretize((&self.declared_labels, &self.declared_macros).into()) - { + match op.clone().concretize( + (&self.declared_labels, &self.declared_macros).into(), + self.hardfork.clone(), + ) { Ok(cop) => cop.assemble(&mut output), Err(ops::Error::ContextIncomplete { source: UnknownLabel { .. }, @@ -579,78 +599,78 @@ mod tests { InstructionMacroDefinition, InstructionMacroInvocation, Terminal, }; use assert_matches::assert_matches; - use etk_ops::cancun::*; + use etk_ops::{cancun::*, HardFork, HardForkOp}; use hex_literal::hex; use num_bigint::{BigInt, Sign}; #[test] fn assemble_variable_push_const_while_pending() -> Result<(), Error> { - let mut asm = Assembler::new(); - let code = vec![ - AbstractOp::Op(Push1(Imm::with_label("label1")).into()), + let mut asm = Assembler::new_with_hardfork(HardFork::Cancun); + let ops = vec![ + AbstractOp::Op(HardForkOp::Cancun(Push1(Imm::with_label("label1")).into())), AbstractOp::Push(Terminal::Number(0xaabb.into()).into()), AbstractOp::Label("label1".into()), ]; - let result = asm.assemble(&code)?; + let result = asm.assemble(&ops)?; assert_eq!(result, hex!("600561aabb")); Ok(()) } #[test] fn assemble_variable_pushes_abab() -> Result<(), Error> { - let mut asm = Assembler::new(); - let code = vec![ - AbstractOp::new(JumpDest), + let mut asm = Assembler::new_with_hardfork(HardFork::Cancun); + let ops = vec![ + AbstractOp::new(HardForkOp::Cancun(JumpDest.into())), AbstractOp::Push(Imm::with_label("label1")), AbstractOp::Push(Imm::with_label("label2")), AbstractOp::Label("label1".into()), - AbstractOp::new(GetPc), + AbstractOp::new(HardForkOp::Cancun(GetPc.into())), AbstractOp::Label("label2".into()), - AbstractOp::new(GetPc), + AbstractOp::new(HardForkOp::Cancun(GetPc.into())), ]; - let result = asm.assemble(&code)?; + let result = asm.assemble(&ops)?; assert_eq!(result, hex!("5b600560065858")); Ok(()) } #[test] fn assemble_variable_pushes_abba() -> Result<(), Error> { - let mut asm = Assembler::new(); - let code = vec![ - AbstractOp::new(JumpDest), + let mut asm = Assembler::new_with_hardfork(HardFork::Cancun); + let ops = vec![ + AbstractOp::new(HardForkOp::Cancun(JumpDest.into())), AbstractOp::Push(Imm::with_label("label1")), AbstractOp::Push(Imm::with_label("label2")), AbstractOp::Label("label2".into()), - AbstractOp::new(GetPc), + AbstractOp::new(HardForkOp::Cancun(GetPc.into())), AbstractOp::Label("label1".into()), - AbstractOp::new(GetPc), + AbstractOp::new(HardForkOp::Cancun(GetPc.into())), ]; - let result = asm.assemble(&code)?; + let result = asm.assemble(&ops)?; assert_eq!(result, hex!("5b600660055858")); Ok(()) } #[test] fn assemble_variable_push1_multiple() -> Result<(), Error> { - let mut asm = Assembler::new(); - let code = vec![ - AbstractOp::new(JumpDest), + let mut asm = Assembler::new_with_hardfork(HardFork::Cancun); + let ops = vec![ + AbstractOp::new(HardForkOp::Cancun(JumpDest.into())), AbstractOp::Push(Imm::with_label("auto")), AbstractOp::Push(Imm::with_label("auto")), AbstractOp::Label("auto".into()), ]; - let result = asm.assemble(&code)?; + let result = asm.assemble(&ops)?; assert_eq!(result, hex!("5b60056005")); Ok(()) } #[test] fn assemble_variable_push_const() -> Result<(), Error> { - let mut asm = Assembler::new(); - let code = vec![AbstractOp::Push( + let mut asm = Assembler::new_with_hardfork(HardFork::Cancun); + let ops = vec![AbstractOp::Push( Terminal::Number((0x00aaaaaaaaaaaaaaaaaaaaaaaa as u128).into()).into(), )]; - let result = asm.assemble(&code)?; + let result = asm.assemble(&ops)?; assert_eq!(result, hex!("6baaaaaaaaaaaaaaaaaaaaaaaa")); Ok(()) } @@ -659,69 +679,69 @@ mod tests { fn assemble_variable_push_too_large() { let v = BigInt::from_bytes_be(Sign::Plus, &[1u8; 33]); - let mut asm = Assembler::new(); - let code = vec![AbstractOp::Push(Terminal::Number(v).into())]; - let err = asm.assemble(&code).unwrap_err(); + let mut asm = Assembler::new_with_hardfork(HardFork::Cancun); + let ops = vec![AbstractOp::Push(Terminal::Number(v).into())]; + let err = asm.assemble(&ops).unwrap_err(); assert_matches!(err, Error::ExpressionTooLarge { .. }); } #[test] fn assemble_variable_push_negative() { - let mut asm = Assembler::new(); - let code = vec![AbstractOp::Push(Terminal::Number((-1).into()).into())]; - let err = asm.assemble(&code).unwrap_err(); + let mut asm = Assembler::new_with_hardfork(HardFork::Cancun); + let ops = vec![AbstractOp::Push(Terminal::Number((-1).into()).into())]; + let err = asm.assemble(&ops).unwrap_err(); assert_matches!(err, Error::ExpressionNegative { .. }); } #[test] fn assemble_variable_push_const0() -> Result<(), Error> { - let mut asm = Assembler::new(); - let code = vec![AbstractOp::Push( + let mut asm = Assembler::new_with_hardfork(HardFork::Cancun); + let ops = vec![AbstractOp::Push( Terminal::Number((0x00 as u128).into()).into(), )]; - let result = asm.assemble(&code)?; + let result = asm.assemble(&ops)?; assert_eq!(result, hex!("6000")); Ok(()) } #[test] fn assemble_variable_push1_known() -> Result<(), Error> { - let mut asm = Assembler::new(); - let code = vec![ - AbstractOp::new(JumpDest), + let mut asm = Assembler::new_with_hardfork(HardFork::Cancun); + let ops = vec![ + AbstractOp::new(HardForkOp::Cancun(JumpDest.into())), AbstractOp::Label("auto".into()), AbstractOp::Push(Imm::with_label("auto")), ]; - let result = asm.assemble(&code)?; + let result = asm.assemble(&ops)?; assert_eq!(result, hex!("5b6001")); Ok(()) } #[test] fn assemble_variable_push1() -> Result<(), Error> { - let mut asm = Assembler::new(); - let code = vec![ + let mut asm = Assembler::new_with_hardfork(HardFork::Cancun); + let ops = vec![ AbstractOp::Push(Imm::with_label("auto")), AbstractOp::Label("auto".into()), - AbstractOp::new(JumpDest), + AbstractOp::new(HardForkOp::Cancun(JumpDest.into())), ]; - let result = asm.assemble(&code)?; + let result = asm.assemble(&ops)?; assert_eq!(result, hex!("60025b")); Ok(()) } #[test] fn assemble_variable_push1_reuse() -> Result<(), Error> { - let mut asm = Assembler::new(); - let code = vec![ + let mut asm = Assembler::new_with_hardfork(HardFork::Cancun); + let ops = vec![ AbstractOp::Push(Imm::with_label("auto")), AbstractOp::Label("auto".into()), - AbstractOp::new(JumpDest), - AbstractOp::new(Push1(Imm::with_label("auto"))), + AbstractOp::new(HardForkOp::Cancun(JumpDest.into())), + AbstractOp::new(HardForkOp::Cancun(Push1(Imm::with_label("auto")).into())), ]; - let result = asm.assemble(&code)?; + let result = asm.assemble(&ops)?; assert_eq!(result, hex!("60025b6002")); Ok(()) } @@ -731,13 +751,13 @@ mod tests { let mut code = vec![]; code.push(AbstractOp::Push(Imm::with_label("auto"))); for _ in 0..255 { - code.push(AbstractOp::new(GetPc)); + code.push(AbstractOp::new(HardForkOp::Cancun(GetPc.into()))); } code.push(AbstractOp::Label("auto".into())); - code.push(AbstractOp::new(JumpDest)); + code.push(AbstractOp::new(HardForkOp::Cancun(JumpDest.into()))); - let mut asm = Assembler::new(); + let mut asm = Assembler::new_with_hardfork(HardFork::Cancun); let result = asm.assemble(&code)?; let mut expected = vec![0x61, 0x01, 0x02]; @@ -753,11 +773,11 @@ mod tests { let mut code = vec![]; code.push(AbstractOp::Push(Imm::with_label("auto"))); for _ in 0..65537 { - code.push(AbstractOp::new(GetPc)); + code.push(AbstractOp::new(HardForkOp::Cancun(GetPc.into()))); } code.push(AbstractOp::Label("auto".into())); - code.push(AbstractOp::new(JumpDest)); + code.push(AbstractOp::new(HardForkOp::Cancun(JumpDest.into()))); let mut asm = Assembler::new(); let result = asm.assemble(&code)?; @@ -773,18 +793,20 @@ mod tests { #[test] fn assemble_undeclared_label() -> Result<(), Error> { - let mut asm = Assembler::new(); - let code = vec![AbstractOp::new(Push1(Imm::with_label("hi")))]; - let err = asm.assemble(&code).unwrap_err(); + let mut asm = Assembler::new_with_hardfork(HardFork::Cancun); + let ops = vec![AbstractOp::new(HardForkOp::Cancun( + Push1(Imm::with_label("hi")).into(), + ))]; + let err = asm.assemble(&ops).unwrap_err(); assert_matches!(err, Error::UndeclaredLabels { labels, .. } if labels == vec!["hi"]); Ok(()) } #[test] fn assemble_jumpdest_no_label() -> Result<(), Error> { - let mut asm = Assembler::new(); - let code = vec![AbstractOp::new(JumpDest)]; - let result = asm.assemble(&code)?; + let mut asm = Assembler::new_with_hardfork(HardFork::Cancun); + let ops = vec![AbstractOp::new(HardForkOp::Cancun(JumpDest.into()))]; + let result = asm.assemble(&ops)?; assert!(asm.declared_labels.is_empty()); assert_eq!(result, hex!("5b")); Ok(()) @@ -792,17 +814,17 @@ mod tests { #[test] fn assemble_jumpdest_with_label() -> Result<(), Error> { - let mut asm = Assembler::new(); - let ops = vec![AbstractOp::Label("lbl".into()), AbstractOp::new(JumpDest)]; + let mut asm = Assembler::new_with_hardfork(HardFork::Cancun); + let ops = vec![ + AbstractOp::Label("lbl".into()), + AbstractOp::new(HardForkOp::Cancun(JumpDest.into())), + ]; let result = asm.assemble(&ops)?; assert_eq!(asm.declared_labels.len(), 1); assert_eq!( asm.declared_labels.get("lbl"), - Some(&Some(LabelDef { - position: 0, - updated: false - })) + Some(&Some(LabelDef { position: 0 })) ); assert_eq!(result, hex!("5b")); Ok(()) @@ -812,11 +834,11 @@ mod tests { fn assemble_jumpdest_jump_with_label() -> Result<(), Error> { let ops = vec![ AbstractOp::Label("lbl".into()), - AbstractOp::new(JumpDest), - AbstractOp::new(Push1(Imm::with_label("lbl"))), + AbstractOp::new(HardForkOp::Cancun(JumpDest.into())), + AbstractOp::new(HardForkOp::Cancun(Push1(Imm::with_label("lbl")).into())), ]; - let mut asm = Assembler::new(); + let mut asm = Assembler::new_with_hardfork(HardFork::Cancun); let result = asm.assemble(&ops)?; assert_eq!(result, hex!("5b6000")); @@ -826,12 +848,12 @@ mod tests { #[test] fn assemble_labeled_pc() -> Result<(), Error> { let ops = vec![ - AbstractOp::new(Push1(Imm::with_label("lbl"))), + AbstractOp::new(HardForkOp::Cancun(Push1(Imm::with_label("lbl")).into())), AbstractOp::Label("lbl".into()), - AbstractOp::new(GetPc), + AbstractOp::new(HardForkOp::Cancun(GetPc.into())), ]; - let mut asm = Assembler::new(); + let mut asm = Assembler::new_with_hardfork(HardFork::Cancun); let result = asm.assemble(&ops)?; assert_eq!(result, hex!("600258")); @@ -841,12 +863,12 @@ mod tests { #[test] fn assemble_jump_jumpdest_with_label() -> Result<(), Error> { let ops = vec![ - AbstractOp::new(Push1(Imm::with_label("lbl"))), + AbstractOp::new(HardForkOp::Cancun(Push1(Imm::with_label("lbl")).into())), AbstractOp::Label("lbl".into()), - AbstractOp::new(JumpDest), + AbstractOp::new(HardForkOp::Cancun(JumpDest.into())), ]; - let mut asm = Assembler::new(); + let mut asm = Assembler::new_with_hardfork(HardFork::Cancun); let result = asm.assemble(&ops)?; assert_eq!(result, hex!("60025b")); @@ -855,26 +877,30 @@ mod tests { #[test] fn assemble_label_too_large() { - let mut ops: Vec<_> = vec![AbstractOp::new(GetPc); 255]; + let mut ops: Vec<_> = vec![AbstractOp::new(HardForkOp::Cancun(GetPc.into())); 255]; ops.push(AbstractOp::Label("b".into())); - ops.push(AbstractOp::new(JumpDest)); + ops.push(AbstractOp::new(HardForkOp::Cancun(JumpDest.into()))); ops.push(AbstractOp::Label("a".into())); - ops.push(AbstractOp::new(JumpDest)); - ops.push(AbstractOp::new(Push1(Imm::with_label("a")))); - let mut asm = Assembler::new(); + ops.push(AbstractOp::new(HardForkOp::Cancun(JumpDest.into()))); + ops.push(AbstractOp::new(HardForkOp::Cancun( + Push1(Imm::with_label("a")).into(), + ))); + let mut asm = Assembler::new_with_hardfork(HardFork::Cancun); let err = asm.assemble(&ops).unwrap_err(); assert_matches!(err, Error::ExpressionTooLarge { expr: Expression::Terminal(Terminal::Label(label)), .. } if label == "a"); } #[test] fn assemble_label_just_right() -> Result<(), Error> { - let mut ops: Vec<_> = vec![AbstractOp::new(GetPc); 255]; + let mut ops: Vec<_> = vec![AbstractOp::new(HardForkOp::Cancun(GetPc.into())); 255]; ops.push(AbstractOp::Label("b".into())); - ops.push(AbstractOp::new(JumpDest)); + ops.push(AbstractOp::new(HardForkOp::Cancun(JumpDest.into()))); ops.push(AbstractOp::Label("a".into())); - ops.push(AbstractOp::new(JumpDest)); - ops.push(AbstractOp::new(Push1(Imm::with_label("b")))); - let mut asm = Assembler::new(); + ops.push(AbstractOp::new(HardForkOp::Cancun(JumpDest.into()))); + ops.push(AbstractOp::new(HardForkOp::Cancun( + Push1(Imm::with_label("b")).into(), + ))); + let mut asm = Assembler::new_with_hardfork(HardFork::Cancun); let result = asm.assemble(&ops)?; let mut expected = vec![0x58; 255]; @@ -913,7 +939,7 @@ mod tests { }), ]; - let mut asm = Assembler::new(); + let mut asm = Assembler::new_with_hardfork(HardFork::Cancun); let result = asm.assemble(&ops)?; assert_eq!(result, []); @@ -928,15 +954,15 @@ mod tests { parameters: vec![], contents: vec![ AbstractOp::Label("a".into()), - AbstractOp::new(JumpDest), - AbstractOp::new(Push1(Imm::with_label("a"))), - AbstractOp::new(Push1(Imm::with_label("b"))), + AbstractOp::new(HardForkOp::Cancun(JumpDest.into())), + AbstractOp::new(HardForkOp::Cancun(Push1(Imm::with_label("a")).into())), + AbstractOp::new(HardForkOp::Cancun(Push1(Imm::with_label("b")).into())), ], } .into(), AbstractOp::Label("b".into()), - AbstractOp::new(JumpDest), - AbstractOp::new(Push1(Imm::with_label("b"))), + AbstractOp::new(HardForkOp::Cancun(JumpDest.into())), + AbstractOp::new(HardForkOp::Cancun(Push1(Imm::with_label("b")).into())), AbstractOp::Macro(InstructionMacroInvocation { name: "my_macro".into(), parameters: vec![], @@ -947,7 +973,7 @@ mod tests { }), ]; - let mut asm = Assembler::new(); + let mut asm = Assembler::new_with_hardfork(HardFork::Cancun); let result = asm.assemble(&ops)?; assert_eq!(result, hex!("5b60005b600360005b60086000")); @@ -962,22 +988,22 @@ mod tests { parameters: vec![], contents: vec![ AbstractOp::Label("a".into()), - AbstractOp::new(JumpDest), - AbstractOp::new(Push1(Imm::with_label("a"))), - AbstractOp::new(Push1(Imm::with_label("b"))), + AbstractOp::new(HardForkOp::Cancun(JumpDest.into())), + AbstractOp::new(HardForkOp::Cancun(Push1(Imm::with_label("a")).into())), + AbstractOp::new(HardForkOp::Cancun(Push1(Imm::with_label("b")).into())), ], } .into(), AbstractOp::Label("b".into()), - AbstractOp::new(JumpDest), - AbstractOp::new(Push1(Imm::with_label("b"))), + AbstractOp::new(HardForkOp::Cancun(JumpDest.into())), + AbstractOp::new(HardForkOp::Cancun(Push1(Imm::with_label("b")).into())), AbstractOp::Macro(InstructionMacroInvocation { name: "my_macro".into(), parameters: vec![], }), ]; - let mut asm = Assembler::new(); + let mut asm = Assembler::new_with_hardfork(HardFork::Cancun); let result = asm.assemble(&ops)?; assert_eq!(result, hex!("5b60005b60036000")); @@ -988,8 +1014,8 @@ mod tests { fn assemble_instruction_macro_delayed_definition() -> Result<(), Error> { let ops = vec![ AbstractOp::Label("b".into()), - AbstractOp::new(JumpDest), - AbstractOp::new(Push1(Imm::with_label("b"))), + AbstractOp::new(HardForkOp::Cancun(JumpDest.into())), + AbstractOp::new(HardForkOp::Cancun(Push1(Imm::with_label("b")).into())), AbstractOp::Macro(InstructionMacroInvocation { name: "my_macro".into(), parameters: vec![], @@ -999,15 +1025,15 @@ mod tests { parameters: vec![], contents: vec![ AbstractOp::Label("a".into()), - AbstractOp::new(JumpDest), - AbstractOp::new(Push1(Imm::with_label("a"))), - AbstractOp::new(Push1(Imm::with_label("b"))), + AbstractOp::new(HardForkOp::Cancun(JumpDest.into())), + AbstractOp::new(HardForkOp::Cancun(Push1(Imm::with_label("a")).into())), + AbstractOp::new(HardForkOp::Cancun(Push1(Imm::with_label("b")).into())), ], } .into(), ]; - let mut asm = Assembler::new(); + let mut asm = Assembler::new_with_hardfork(HardFork::Cancun); let result = asm.assemble(&ops)?; assert_eq!(result, hex!("5b60005b60036000")); @@ -1025,19 +1051,19 @@ mod tests { name: "my_macro".into(), parameters: vec![], contents: vec![ - AbstractOp::new(JumpDest), + AbstractOp::new(HardForkOp::Cancun(JumpDest.into())), AbstractOp::Push(Imm::with_label("label1")), AbstractOp::Push(Imm::with_label("label2")), AbstractOp::Label("label1".into()), - AbstractOp::new(GetPc), + AbstractOp::new(HardForkOp::Cancun(GetPc.into())), AbstractOp::Label("label2".into()), - AbstractOp::new(GetPc), + AbstractOp::new(HardForkOp::Cancun(GetPc.into())), ], } .into(), ]; - let mut asm = Assembler::new(); + let mut asm = Assembler::new_with_hardfork(HardFork::Cancun); let result = asm.assemble(&ops)?; assert_eq!(result, hex!("5b600560065858")); @@ -1049,7 +1075,7 @@ mod tests { let ops = vec![AbstractOp::Macro( InstructionMacroInvocation::with_zero_parameters("my_macro".into()), )]; - let mut asm = Assembler::new(); + let mut asm = Assembler::new_with_hardfork(HardFork::Cancun); let err = asm.assemble(&ops).unwrap_err(); assert_matches!(err, Error::UndeclaredInstructionMacro { name, .. } if name == "my_macro"); @@ -1062,17 +1088,17 @@ mod tests { InstructionMacroDefinition { name: "my_macro".into(), parameters: vec![], - contents: vec![AbstractOp::new(Caller)], + contents: vec![AbstractOp::new(HardForkOp::Cancun(Caller.into()))], } .into(), InstructionMacroDefinition { name: "my_macro".into(), parameters: vec![], - contents: vec![AbstractOp::new(Caller)], + contents: vec![AbstractOp::new(HardForkOp::Cancun(Caller.into()))], } .into(), ]; - let mut asm = Assembler::new(); + let mut asm = Assembler::new_with_hardfork(HardFork::Cancun); let err = asm.assemble(&ops).unwrap_err(); assert_matches!(err, Error::DuplicateMacro { name, .. } if name == "my_macro"); @@ -1092,7 +1118,7 @@ mod tests { "my_macro".into(), )), ]; - let mut asm = Assembler::new(); + let mut asm = Assembler::new_with_hardfork(HardFork::Cancun); let err = asm.assemble(&ops).unwrap_err(); assert_matches!(err, Error::DuplicateLabel { label, .. } if label == "a"); @@ -1104,22 +1130,22 @@ mod tests { fn assemble_conflicting_labels_in_instruction_macro() -> Result<(), Error> { let ops = vec![ AbstractOp::Label("a".into()), - AbstractOp::new(Caller), + AbstractOp::new(HardForkOp::Cancun(Caller.into())), InstructionMacroDefinition { name: "my_macro()".into(), parameters: vec![], contents: vec![ AbstractOp::Label("a".into()), - AbstractOp::new(Push1(Imm::with_label("a"))), + AbstractOp::new(HardForkOp::Cancun(Push1(Imm::with_label("a")).into())), ], } .into(), AbstractOp::Macro(InstructionMacroInvocation::with_zero_parameters( "my_macro()".into(), )), - AbstractOp::new(Push1(Imm::with_label("a"))), + AbstractOp::new(HardForkOp::Cancun(Push1(Imm::with_label("a")).into())), ]; - let mut asm = Assembler::new(); + let mut asm = Assembler::new_with_hardfork(HardFork::Cancun); let result = asm.assemble(&ops)?; assert_eq!(result, hex!("3360016000")); @@ -1134,14 +1160,14 @@ mod tests { name: "my_macro".into(), parameters: vec!["foo".into(), "bar".into()], contents: vec![ - AbstractOp::new(Push1(Imm::with_variable("foo"))), - AbstractOp::new(Push1(Imm::with_variable("bar"))), + AbstractOp::new(HardForkOp::Cancun(Push1(Imm::with_variable("foo")).into())), + AbstractOp::new(HardForkOp::Cancun(Push1(Imm::with_variable("bar")).into())), ], } .into(), AbstractOp::Label("b".into()), - AbstractOp::new(JumpDest), - AbstractOp::new(Push1(Imm::with_label("b"))), + AbstractOp::new(HardForkOp::Cancun(JumpDest.into())), + AbstractOp::new(HardForkOp::Cancun(Push1(Imm::with_label("b")).into())), AbstractOp::Macro(InstructionMacroInvocation { name: "my_macro".into(), parameters: vec![ @@ -1151,7 +1177,7 @@ mod tests { }), ]; - let mut asm = Assembler::new(); + let mut asm = Assembler::new_with_hardfork(HardFork::Cancun); let result = asm.assemble(&ops)?; assert_eq!(result, hex!("5b600060426000")); @@ -1160,11 +1186,11 @@ mod tests { #[test] fn assemble_expression_push() -> Result<(), Error> { - let ops = vec![AbstractOp::new(Push1(Imm::with_expression( - Expression::Plus(1.into(), 1.into()), - )))]; + let ops = vec![AbstractOp::new(HardForkOp::Cancun( + Push1(Imm::with_expression(Expression::Plus(1.into(), 1.into()))).into(), + ))]; - let mut asm = Assembler::new(); + let mut asm = Assembler::new_with_hardfork(HardFork::Cancun); let result = asm.assemble(&ops)?; assert_eq!(result, hex!("6002")); @@ -1173,10 +1199,10 @@ mod tests { #[test] fn assemble_expression_negative() -> Result<(), Error> { - let ops = vec![AbstractOp::new(Push1(Imm::with_expression( - BigInt::from(-1).into(), - )))]; - let mut asm = Assembler::new(); + let ops = vec![AbstractOp::new(HardForkOp::Cancun( + Push1(Imm::with_expression(BigInt::from(-1).into())).into(), + ))]; + let mut asm = Assembler::new_with_hardfork(HardFork::Cancun); let err = asm.assemble(&ops).unwrap_err(); assert_matches!(err, Error::ExpressionNegative { value, .. } if value == BigInt::from(-1)); @@ -1185,10 +1211,13 @@ mod tests { #[test] fn assemble_expression_undeclared_label() -> Result<(), Error> { - let mut asm = Assembler::new(); - let ops = vec![AbstractOp::new(Push1(Imm::with_expression( - Terminal::Label(String::from("hi")).into(), - )))]; + let mut asm = Assembler::new_with_hardfork(HardFork::Cancun); + let ops = vec![AbstractOp::new(HardForkOp::Cancun( + Push1(Imm::with_expression( + Terminal::Label(String::from("hi")).into(), + )) + .into(), + ))]; let err = asm.assemble(&ops).unwrap_err(); assert_matches!(err, Error::UndeclaredLabels { labels, .. } if labels == vec!["hi"]); Ok(()) @@ -1202,7 +1231,7 @@ mod tests { Terminal::Label("foo".into()).into(), BigInt::from(256).into(), ))), - AbstractOp::new(Push2(Imm::with_label("foo1"))), + AbstractOp::new(HardForkOp::Cancun(Push2(Imm::with_label("foo1")).into())), AbstractOp::Label("foo".into()), AbstractOp::Label("foo1".into()), ]; @@ -1219,7 +1248,7 @@ mod tests { Terminal::Label("z".into()).into(), BigInt::from(256).into(), ))), - AbstractOp::new(Push2(Imm::with_label("a"))), + AbstractOp::new(HardForkOp::Cancun(Push2(Imm::with_label("a")).into())), AbstractOp::Label("z".into()), AbstractOp::Label("a".into()), ]; @@ -1236,14 +1265,14 @@ mod tests { Terminal::Label("foo".into()).into(), BigInt::from(256).into(), ))), - AbstractOp::new(Push2(Imm::with_label("foo"))), + AbstractOp::new(HardForkOp::Cancun(Push2(Imm::with_label("foo")).into())), AbstractOp::Push(Imm::with_expression(Expression::Plus( Terminal::Label("bar".into()).into(), BigInt::from(256).into(), ))), - AbstractOp::new(Gas), + AbstractOp::new(HardForkOp::Cancun(Gas.into())), AbstractOp::Label("foo".into()), - AbstractOp::new(Gas), + AbstractOp::new(HardForkOp::Cancun(Gas.into())), AbstractOp::Label("bar".into()), ]; let result = asm.assemble(&ops)?; @@ -1260,7 +1289,7 @@ mod tests { Terminal::Label("foo".into()).into(), BigInt::from(256).into(), ))), - AbstractOp::new(Push2(Imm::with_label("foo1"))), + AbstractOp::new(HardForkOp::Cancun(Push2(Imm::with_label("foo1")).into())), AbstractOp::Push(Imm::with_expression(Expression::Plus( Terminal::Label("foo".into()).into(), BigInt::from(256).into(), @@ -1281,12 +1310,12 @@ mod tests { Terminal::Label("foo".into()).into(), BigInt::from(256).into(), ))), - AbstractOp::new(Push2(Imm::with_label("foo1"))), + AbstractOp::new(HardForkOp::Cancun(Push2(Imm::with_label("foo1")).into())), AbstractOp::Push(Imm::with_expression(Expression::Plus( Terminal::Label("foo".into()).into(), BigInt::from(256).into(), ))), - AbstractOp::new(Push2(Imm::with_label("foo1"))), + AbstractOp::new(HardForkOp::Cancun(Push2(Imm::with_label("foo1")).into())), AbstractOp::Label("foo".into()), AbstractOp::Label("foo1".into()), ]; @@ -1297,14 +1326,14 @@ mod tests { #[test] fn assemble_variable_push_expression_with_undeclared_labels() -> Result<(), Error> { - let mut asm = Assembler::new(); + let mut asm = Assembler::new_with_hardfork(HardFork::Cancun); let ops = vec![ - AbstractOp::new(JumpDest), + AbstractOp::new(HardForkOp::Cancun(JumpDest.into())), AbstractOp::Push(Imm::with_expression(Expression::Plus( Terminal::Label("foo".into()).into(), Terminal::Label("bar".into()).into(), ))), - AbstractOp::new(Gas), + AbstractOp::new(HardForkOp::Cancun(Gas.into())), ]; let err = asm.assemble(&ops).unwrap_err(); // The expressions have short-circuit evaluation, so only the first label is caught in the error. @@ -1321,15 +1350,18 @@ mod tests { // pc # repeat 126 times. // lbl1: // lbl2: - let mut ops = vec![AbstractOp::new(GetPc); 130]; + let mut ops = vec![AbstractOp::new(HardForkOp::Cancun(GetPc.into())); 130]; ops[0] = AbstractOp::Push(Imm::with_expression(Expression::Minus( Terminal::Label(String::from("lbl1")).into(), Terminal::Label(String::from("lbl2")).into(), ))); - ops[1] = AbstractOp::new(Push2( - Expression::Plus( - Terminal::Label(String::from("lbl1")).into(), - Terminal::Label(String::from("lbl2")).into(), + ops[1] = AbstractOp::new(HardForkOp::Cancun( + Push2( + Expression::Plus( + Terminal::Label(String::from("lbl1")).into(), + Terminal::Label(String::from("lbl2")).into(), + ) + .into(), ) .into(), )); @@ -1362,9 +1394,9 @@ mod tests { #[test] fn assemble_variable_push1_expression() -> Result<(), Error> { - let mut asm = Assembler::new(); + let mut asm = Assembler::new_with_hardfork(HardFork::Cancun); let ops = vec![ - AbstractOp::new(JumpDest), + AbstractOp::new(HardForkOp::Cancun(JumpDest.into())), AbstractOp::Label("auto".into()), AbstractOp::Push(Imm::with_expression(Expression::Plus( 1.into(), @@ -1378,14 +1410,14 @@ mod tests { #[test] fn assemble_expression_with_labels() -> Result<(), Error> { - let mut asm = Assembler::new(); + let mut asm = Assembler::new_with_hardfork(HardFork::Cancun); let ops = vec![ - AbstractOp::new(JumpDest), + AbstractOp::new(HardForkOp::Cancun(JumpDest.into())), AbstractOp::Push(Imm::with_expression(Expression::Plus( Terminal::Label(String::from("foo")).into(), Terminal::Label(String::from("bar")).into(), ))), - AbstractOp::new(Gas), + AbstractOp::new(HardForkOp::Cancun(Gas.into())), AbstractOp::Label("foo".into()), AbstractOp::Label("bar".into()), ]; @@ -1403,13 +1435,16 @@ mod tests { content: Imm::with_expression(Expression::Plus(1.into(), 1.into())), } .into(), - AbstractOp::new(Push1(Imm::with_macro(ExpressionMacroInvocation { - name: "foo".into(), - parameters: vec![], - }))), + AbstractOp::new(HardForkOp::Cancun( + Push1(Imm::with_macro(ExpressionMacroInvocation { + name: "foo".into(), + parameters: vec![], + })) + .into(), + )), ]; - let mut asm = Assembler::new(); + let mut asm = Assembler::new_with_hardfork(HardFork::Cancun); let result = asm.assemble(&ops)?; assert_eq!(result, hex!("6002")); @@ -1422,19 +1457,21 @@ mod tests { InstructionMacroDefinition { name: "my_macro".into(), parameters: vec!["foo".into()], - contents: vec![AbstractOp::new(Push1(Imm::with_variable("bar")))], + contents: vec![AbstractOp::new(HardForkOp::Cancun( + Push1(Imm::with_variable("bar")).into(), + ))], } .into(), AbstractOp::Label("b".into()), - AbstractOp::new(JumpDest), - AbstractOp::new(Push1(Imm::with_label("b"))), + AbstractOp::new(HardForkOp::Cancun(JumpDest.into())), + AbstractOp::new(HardForkOp::Cancun(Push1(Imm::with_label("b")).into())), AbstractOp::Macro(InstructionMacroInvocation { name: "my_macro".into(), parameters: vec![BigInt::from_bytes_be(Sign::Plus, &vec![0x42]).into()], }), ]; - let mut asm = Assembler::new(); + let mut asm = Assembler::new_with_hardfork(HardFork::Cancun); let err = asm.assemble(&ops).unwrap_err(); assert_matches!(err, Error::UndeclaredVariableMacro { var, .. } if var == "bar"); @@ -1443,7 +1480,7 @@ mod tests { #[test] fn assemble_instruction_macro_two_delayed_definitions_mirrored() -> Result<(), Error> { let ops = vec![ - AbstractOp::new(GetPc), + AbstractOp::new(HardForkOp::Cancun(GetPc.into())), AbstractOp::Macro(InstructionMacroInvocation { name: "macro1".into(), parameters: vec![], @@ -1455,18 +1492,18 @@ mod tests { InstructionMacroDefinition { name: "macro0".into(), parameters: vec![], - contents: vec![AbstractOp::new(JumpDest)], + contents: vec![AbstractOp::new(HardForkOp::Cancun(JumpDest.into()))], } .into(), InstructionMacroDefinition { name: "macro1".into(), parameters: vec![], - contents: vec![AbstractOp::new(Caller)], + contents: vec![AbstractOp::new(HardForkOp::Cancun(Caller.into()))], } .into(), ]; - let mut asm = Assembler::new(); + let mut asm = Assembler::new_with_hardfork(HardFork::Cancun); let result = asm.assemble(&ops)?; assert_eq!(result, hex!("58335b")); @@ -1476,7 +1513,7 @@ mod tests { #[test] fn assemble_instruction_macro_two_delayed_definitions() -> Result<(), Error> { let ops = vec![ - AbstractOp::new(GetPc), + AbstractOp::new(HardForkOp::Cancun(GetPc.into())), AbstractOp::Macro(InstructionMacroInvocation { name: "macro0".into(), parameters: vec![], @@ -1488,18 +1525,18 @@ mod tests { InstructionMacroDefinition { name: "macro0".into(), parameters: vec![], - contents: vec![AbstractOp::new(JumpDest)], + contents: vec![AbstractOp::new(HardForkOp::Cancun(JumpDest.into()))], } .into(), InstructionMacroDefinition { name: "macro1".into(), parameters: vec![], - contents: vec![AbstractOp::new(Caller)], + contents: vec![AbstractOp::new(HardForkOp::Cancun(Caller.into()))], } .into(), ]; - let mut asm = Assembler::new(); + let mut asm = Assembler::new_with_hardfork(HardFork::Cancun); let result = asm.assemble(&ops)?; assert_eq!(result, hex!("585b33")); diff --git a/etk-asm/src/ast.rs b/etk-asm/src/ast.rs index e3824c1..ea1278a 100644 --- a/etk-asm/src/ast.rs +++ b/etk-asm/src/ast.rs @@ -1,7 +1,10 @@ use std::path::PathBuf; use crate::ops::{Abstract, AbstractOp, ExpressionMacroDefinition, InstructionMacroDefinition}; -use etk_ops::cancun::Op; +use etk_ops::cancun::Op as CancunOp; +use etk_ops::london::Op as LondonOp; +use etk_ops::shanghai::Op as ShanghaiOp; +use etk_ops::HardForkDirective; #[derive(Debug, Clone, PartialEq)] pub(crate) enum Node { @@ -9,10 +12,23 @@ pub(crate) enum Node { Import(PathBuf), Include(PathBuf), IncludeHex(PathBuf), + HardforkMacro((HardForkDirective, Option)), } -impl From> for Node { - fn from(op: Op) -> Self { - Node::Op(AbstractOp::Op(op)) +impl From> for Node { + fn from(op: CancunOp) -> Self { + Node::Op(AbstractOp::Op(etk_ops::HardForkOp::Cancun(op))) + } +} + +impl From> for Node { + fn from(op: ShanghaiOp) -> Self { + Node::Op(AbstractOp::Op(etk_ops::HardForkOp::Shanghai(op))) + } +} + +impl From> for Node { + fn from(op: LondonOp) -> Self { + Node::Op(AbstractOp::Op(etk_ops::HardForkOp::London(op))) } } diff --git a/etk-asm/src/bin/eas.rs b/etk-asm/src/bin/eas.rs index cf3285c..e0ceb9b 100644 --- a/etk-asm/src/bin/eas.rs +++ b/etk-asm/src/bin/eas.rs @@ -2,6 +2,7 @@ use etk_cli::errors::WithSources; use etk_cli::io::HexWrite; use etk_asm::ingest::{Error, Ingest}; +use etk_ops::HardFork; use std::fs::File; use std::io::prelude::*; @@ -16,6 +17,9 @@ struct Opt { input: PathBuf, #[structopt(parse(from_os_str))] out: Option, + + #[structopt(long)] + hardfork: Option, } fn create(path: PathBuf) -> File { @@ -43,9 +47,16 @@ fn run() -> Result<(), Error> { None => Box::new(std::io::stdout()), }; + let hardfork = match opt.hardfork { + Some(h) => h, + None => HardFork::default(), + }; + + println!("Hardfork selected: {:?}.", hardfork); + let hex_out = HexWrite::new(&mut out); - let mut ingest = Ingest::new(hex_out); + let mut ingest = Ingest::new(hex_out, hardfork); ingest.ingest_file(opt.input)?; out.write_all(b"\n").unwrap(); diff --git a/etk-asm/src/ingest.rs b/etk-asm/src/ingest.rs index 0cef990..9acfc27 100644 --- a/etk-asm/src/ingest.rs +++ b/etk-asm/src/ingest.rs @@ -5,6 +5,8 @@ mod error { use crate::asm::Error as AssembleError; use crate::ParseError; + use etk_ops::HardFork; + use etk_ops::HardForkDirective; use snafu::{Backtrace, Snafu}; use std::path::PathBuf; @@ -93,6 +95,22 @@ mod error { /// The location of the error. backtrace: Backtrace, }, + + /// Hardfork set for compilation is out of range. + #[snafu(display( + "Hardfork set for compilation was `{}` but `{}` is required.", + hardfork, + directive + ))] + #[non_exhaustive] + OutOfRangeHardfork { + /// Hardfork set for compilation. + hardfork: HardFork, + /// Directive that breaks the compilation. + directive: HardForkDirective, + /// The location of the error. + backtrace: Backtrace, + }, } } @@ -102,6 +120,7 @@ use crate::parse::parse_asm; pub use self::error::Error; +use etk_ops::HardFork; use snafu::{ensure, ResultExt}; use std::fs::{read_to_string, File}; @@ -227,6 +246,7 @@ impl Program { /// /// ```rust /// use etk_asm::ingest::Ingest; +/// use etk_ops::HardFork; /// # /// # use etk_asm::ingest::Error; /// # @@ -239,7 +259,7 @@ impl Program { /// "#; /// /// let mut output = Vec::new(); -/// let mut ingest = Ingest::new(&mut output); +/// let mut ingest = Ingest::new(&mut output, HardFork::Cancun); /// ingest.ingest("./example.etk", &text)?; /// /// # let expected = hex!("6100035b"); @@ -249,12 +269,13 @@ impl Program { #[derive(Debug)] pub struct Ingest { output: W, + hardfork: HardFork, } impl Ingest { /// Make a new `Ingest` that writes assembled bytes to `output`. - pub fn new(output: W) -> Self { - Self { output } + pub fn new(output: W, hardfork: HardFork) -> Self { + Self { output, hardfork } } } @@ -285,12 +306,12 @@ where /// Assemble instructions from `src` as if they were read from a file located /// at `path`. - pub fn ingest

(&mut self, path: P, src: &str) -> Result<(), Error> + pub fn ingest

(&mut self, path: P, text: &str) -> Result<(), Error> where P: Into, { let mut program = Program::new(path.into()); - let nodes = self.preprocess(&mut program, src)?; + let nodes = self.preprocess(&mut program, text)?; let mut asm = Assembler::new(); let raw = asm.assemble(&nodes)?; @@ -303,7 +324,7 @@ where } fn preprocess(&mut self, program: &mut Program, src: &str) -> Result, Error> { - let nodes = parse_asm(src).with_context(|_| error::Parse { + let nodes = parse_asm(src, self.hardfork.clone()).context(error::Parse { path: program.sources.last().unwrap().clone(), })?; let mut raws = Vec::new(); @@ -334,6 +355,27 @@ where raws.push(RawOp::Raw(raw)) } + Node::HardforkMacro(directive) => { + // Here, `directive`` is always a valid range. + let (hfd1, ophfd2) = directive; + ensure!( + self.hardfork.is_valid(&hfd1), + error::OutOfRangeHardfork { + hardfork: self.hardfork.clone(), + directive: hfd1, + } + ); + + if let Some(hfd2) = ophfd2 { + ensure!( + self.hardfork.is_valid(&hfd2), + error::OutOfRangeHardfork { + hardfork: self.hardfork.clone(), + directive: hfd2, + } + ); + } + } } } @@ -393,7 +435,7 @@ mod tests { ); let mut output = Vec::new(); - let mut ingest = Ingest::new(&mut output); + let mut ingest = Ingest::new(&mut output, HardFork::Cancun); ingest.ingest(root, &text)?; assert_eq!(output, hex!("6001602a6002")); @@ -422,7 +464,7 @@ mod tests { ); let mut output = Vec::new(); - let mut ingest = Ingest::new(&mut output); + let mut ingest = Ingest::new(&mut output, HardFork::Cancun); ingest.ingest(root, &text)?; assert_eq!(output, hex!("60015b586000566002")); @@ -451,7 +493,7 @@ mod tests { ); let mut output = Vec::new(); - let mut ingest = Ingest::new(&mut output); + let mut ingest = Ingest::new(&mut output, HardFork::Cancun); let err = ingest.ingest(root, &text).unwrap_err(); assert_matches!( @@ -476,7 +518,7 @@ mod tests { ); let mut output = Vec::new(); - let mut ingest = Ingest::new(&mut output); + let mut ingest = Ingest::new(&mut output, HardFork::Cancun); ingest.ingest(root, &text)?; assert_eq!(output, hex!("6001deadbeef0102f66002")); @@ -500,7 +542,7 @@ mod tests { ); let mut output = Vec::new(); - let mut ingest = Ingest::new(&mut output); + let mut ingest = Ingest::new(&mut output, HardFork::Cancun); ingest.ingest(root, &text)?; assert_eq!(output, hex!("6001deadbeef0102f65b600960ff")); @@ -522,7 +564,7 @@ mod tests { ); let mut output = Vec::new(); - let mut ingest = Ingest::new(&mut output); + let mut ingest = Ingest::new(&mut output, HardFork::Cancun); ingest.ingest(root, &text)?; let expected = hex!("61001caaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa5b"); @@ -565,7 +607,7 @@ mod tests { ); let mut output = Vec::new(); - let mut ingest = Ingest::new(&mut output); + let mut ingest = Ingest::new(&mut output, HardFork::Cancun); ingest.ingest(root, &text)?; let expected = hex!("620000096200000e5b5b6008600e5b610008610009"); @@ -614,7 +656,7 @@ mod tests { ); let mut output = Vec::new(); - let mut ingest = Ingest::new(&mut output); + let mut ingest = Ingest::new(&mut output, HardFork::Cancun); ingest.ingest(root, &text)?; let expected = hex!("620000155b58600b5b5b61000b6100035b600360045b62000004"); @@ -635,7 +677,7 @@ mod tests { ); let mut output = Vec::new(); - let mut ingest = Ingest::new(&mut output); + let mut ingest = Ingest::new(&mut output, HardFork::Cancun); let root = std::env::current_exe().unwrap(); let err = ingest.ingest(root, &text).unwrap_err(); @@ -656,7 +698,7 @@ mod tests { ); let mut output = Vec::new(); - let mut ingest = Ingest::new(&mut output); + let mut ingest = Ingest::new(&mut output, HardFork::Cancun); let err = ingest.ingest(root, &text).unwrap_err(); assert_matches!(err, Error::RecursionLimit { .. }); diff --git a/etk-asm/src/ops.rs b/etk-asm/src/ops.rs index 1749459..45fe7ce 100644 --- a/etk-asm/src/ops.rs +++ b/etk-asm/src/ops.rs @@ -2,7 +2,7 @@ mod error { use super::expression; - use etk_ops::cancun::Op; + use etk_ops::HardForkOp; use num_bigint::BigInt; use snafu::{Backtrace, Snafu}; @@ -16,7 +16,7 @@ mod error { ExpressionTooLarge { source: std::array::TryFromSliceError, value: BigInt, - spec: Op<()>, + spec: HardForkOp<()>, backtrace: Backtrace, }, ExpressionNegative { @@ -43,7 +43,12 @@ mod types; pub(crate) use self::error::Error; -use etk_ops::cancun::{Op, Operation, Push32}; +use etk_ops::cancun::Push32 as CancunPush32; +use etk_ops::london::Push32 as LondonPush32; +use etk_ops::shanghai::Push32 as ShanghaiPush32; +use etk_ops::HardFork; +use etk_ops::HardForkOp; +use etk_ops::Operation; pub use self::error::UnknownSpecifierError; pub use self::expression::{Context, Expression, Terminal}; @@ -84,13 +89,13 @@ pub(crate) trait Concretize { fn concretize(&self, ctx: Context) -> Result; } -impl Concretize for Op { - type Concrete = Op<[u8]>; +impl Concretize for HardForkOp { + type Concrete = HardForkOp<[u8]>; fn concretize(&self, ctx: Context) -> Result { let expr = match self.immediate() { Some(i) => &i.tree, - None => return Ok(Op::new(self.code()).unwrap()), + None => return Ok(HardForkOp::new_op(self.code()).unwrap()), }; let value = expr @@ -110,13 +115,38 @@ impl Concretize for Op { bytes = new; } - let result = self - .code() - .with(bytes.as_slice()) - .context(error::ExpressionTooLarge { - value, - spec: self.code(), - })?; + let result = match &self { + HardForkOp::Cancun(op) => { + let op_context = + op.code() + .with(bytes.as_slice()) + .context(error::ExpressionTooLarge { + value, + spec: self.code(), + })?; + HardForkOp::Cancun(op_context) + } + HardForkOp::Shanghai(op) => { + let op_context = + op.code() + .with(bytes.as_slice()) + .context(error::ExpressionTooLarge { + value, + spec: self.code(), + })?; + HardForkOp::Shanghai(op_context) + } + HardForkOp::London(op) => { + let op_context = + op.code() + .with(bytes.as_slice()) + .context(error::ExpressionTooLarge { + value, + spec: self.code(), + })?; + HardForkOp::London(op_context) + } + }; Ok(result) } @@ -172,7 +202,7 @@ impl Access { #[derive(Debug, Clone, Eq, PartialEq)] pub enum AbstractOp { /// A real `Op`, as opposed to a label or variable sized push. - Op(Op), + Op(HardForkOp), /// A label, which is a virtual instruction. Label(String), @@ -191,12 +221,16 @@ impl AbstractOp { /// Construct a new `AbstractOp` from an `Operation`. pub fn new(op: O) -> Self where - O: Into>, + O: Into>, { Self::Op(op.into()) } - pub(crate) fn concretize(self, ctx: Context) -> Result, error::Error> { + pub(crate) fn concretize( + self, + ctx: Context, + hardfork: HardFork, + ) -> Result, error::Error> { match self { Self::Op(op) => op.concretize(ctx), Self::Push(imm) => { @@ -213,21 +247,44 @@ impl AbstractOp { ); if bytes.len() > 32 { + let push32 = match hardfork { + HardFork::Cancun => { + HardForkOp::Cancun(etk_ops::cancun::Op::Push32(CancunPush32(()))) + } + HardFork::Shanghai => { + HardForkOp::Shanghai(etk_ops::shanghai::Op::Push32(ShanghaiPush32(()))) + } + HardFork::London => { + HardForkOp::London(etk_ops::london::Op::Push32(LondonPush32(()))) + } + }; // TODO: Fix hack to get a TryFromSliceError. let err = <[u8; 32]>::try_from(bytes.as_slice()) .context(error::ExpressionTooLarge { value, - spec: Push32(()), + spec: push32, }) .unwrap_err(); return Err(err); } let size = std::cmp::max(1, (value.bits() + 8 - 1) / 8); - let spec = Op::<()>::push(size.try_into().unwrap()).unwrap(); + //let spec = Op::<()>::push(size.try_into().unwrap()).unwrap(); + + let spec = match hardfork { + HardFork::Cancun => HardForkOp::Cancun( + etk_ops::cancun::Op::<()>::push(size.try_into().unwrap()).unwrap(), + ), + HardFork::Shanghai => HardForkOp::Shanghai( + etk_ops::shanghai::Op::<()>::push(size.try_into().unwrap()).unwrap(), + ), + HardFork::London => HardForkOp::London( + etk_ops::london::Op::<()>::push(size.try_into().unwrap()).unwrap(), + ), + }; let start = bytes.len() + 1 - spec.size(); - AbstractOp::new(spec.with(&bytes[start..]).unwrap()).concretize(ctx) + AbstractOp::new(spec.with(&bytes[start..]).unwrap()).concretize(ctx, hardfork) } Self::Label(_) => panic!("labels cannot be concretized"), Self::Macro(_) => panic!("macros cannot be concretized"), @@ -269,7 +326,7 @@ impl AbstractOp { } /// Return the specifier that corresponds to this `AbstractOp`. - pub fn specifier(&self) -> Option> { + pub fn specifier(&self) -> Option> { match self { Self::Op(op) => Some(op.code()), _ => None, @@ -277,12 +334,12 @@ impl AbstractOp { } } -impl From> for AbstractOp { - fn from(cop: Op<[u8]>) -> Self { +impl From> for AbstractOp { + fn from(cop: HardForkOp<[u8]>) -> Self { let code = cop.code(); let cop = match cop.into_immediate() { Some(i) => code.with(i).unwrap(), - None => Op::new(code).unwrap(), + None => HardForkOp::new_op(code).unwrap(), }; Self::Op(cop) } diff --git a/etk-asm/src/parse/asm.pest b/etk-asm/src/parse/asm.pest index 19c9f1b..3547fa1 100644 --- a/etk-asm/src/parse/asm.pest +++ b/etk-asm/src/parse/asm.pest @@ -40,17 +40,28 @@ instruction_macro_variable = @{ "$" ~ function_parameter } instruction_macro = !{ "%" ~ function_invocation } local_macro = { !builtin ~ (instruction_macro_definition | instruction_macro | expression_macro_definition) } -builtin = ${ "%" ~ ( import | include | include_hex | push_macro ) } +builtin = ${ "%" ~ (import | include | include_hex | push_macro | hardfork) } import = !{ "import" ~ arguments } include = !{ "include" ~ arguments } include_hex = !{ "include_hex" ~ arguments } push_macro = !{ "push" ~ arguments } +hardfork = !{"hardfork" ~ hardfork_arguments } arguments = _{ "(" ~ arguments_list? ~ ")" } arguments_list = _{ ( argument ~ "," )* ~ argument? } argument = _{ string | expression } +hardfork_arguments = _{ "(" ~ hardfork_arguments_list ~ ")" } +hardfork_arguments_list = _{ "\"" ~ ( hardfork_argument ~ "," )* ~ hardfork_argument? ~ "\"" } +hardfork_argument = { hardfork_operator? ~ hardfork_name } +hardfork_operator = _{ lte | gte | gt | lt } +gt = { ">" } +lt = { "<" } +gte = { ">=" } +lte = { "<=" } +hardfork_name = { ASCII_ALPHA* } + string = @{ "\"" ~ string_char* ~ "\"" } string_char = _{ "\\\\" | "\\\"" | (!"\\" ~ !"\"" ~ ANY) } diff --git a/etk-asm/src/parse/error.rs b/etk-asm/src/parse/error.rs index ccc89e9..c622356 100644 --- a/etk-asm/src/parse/error.rs +++ b/etk-asm/src/parse/error.rs @@ -1,3 +1,6 @@ +use std::path::PathBuf; + +use etk_ops::HardForkDirective; use pest::error::Error; use snafu::{Backtrace, IntoError, Snafu}; @@ -60,6 +63,96 @@ pub enum ParseError { /// The location of the error. backtrace: Backtrace, }, + + /// An argument provided to a macro was of the wrong type. + #[snafu(display("File {} does not exist", path.to_string_lossy()))] + #[non_exhaustive] + FileNotFound { + /// Path to the offending file. + path: PathBuf, + + /// The location of the error. + backtrace: Backtrace, + }, + + /// An included fail failed to parse as hexadecimal. + #[snafu(display("included file `{}` is invalid hex: {}", path.to_string_lossy(), source))] + #[non_exhaustive] + InvalidHex { + /// Path to the offending file. + path: PathBuf, + + /// The underlying source of this error. + source: Box, + + /// The location of the error. + backtrace: Backtrace, + }, + + /// Hardfork defined inside macro is invalid. + #[snafu(display("hardfork `{}` is invalid", hardfork))] + #[non_exhaustive] + InvalidHardfork { + /// Name of the invalid hardfork. + hardfork: String, + + /// The location of the error. + backtrace: Backtrace, + }, + + /// Range of Hardforks exceeded max amout. + #[snafu(display("Expected range of two hardfork max, but got {}.", parsed))] + #[non_exhaustive] + ExceededRangeHardfork { + /// Number of hardforks parsed. + parsed: usize, + /// The location of the error. + backtrace: Backtrace, + }, + + /// Range of hardforks is invalid + #[snafu(display( + "For a range, both hardforks needs to have operators: {},{}.", + directive0, + directive1 + ))] + #[non_exhaustive] + InvalidRangeHardfork { + /// Directive with invalid range. + directive0: HardForkDirective, + /// Directive with invalid range. + directive1: HardForkDirective, + /// The location of the error. + backtrace: Backtrace, + }, + + /// Range of hardforks overlap and should be simplified. + #[snafu(display( + "Range of hardforks overlap and should be simplified: {},{}.", + directive0, + directive1 + ))] + #[non_exhaustive] + OverlappingRangeHardfork { + /// Directive with invalid range. + directive0: HardForkDirective, + /// Directive with invalid range. + directive1: HardForkDirective, + /// The location of the error. + backtrace: Backtrace, + }, + + /// Range of hardforks is empty. + #[snafu(display("Range of hardforks is empty: {},{}.", directive0, directive1))] + #[non_exhaustive] + EmptyRangeHardfork { + /// Directive with invalid range. + directive0: HardForkDirective, + /// Directive with invalid range. + directive1: HardForkDirective, + /// The location of the error. + backtrace: Backtrace, + }, } impl From> for ParseError { diff --git a/etk-asm/src/parse/macros.rs b/etk-asm/src/parse/macros.rs index c262917..fdcaeb7 100644 --- a/etk-asm/src/parse/macros.rs +++ b/etk-asm/src/parse/macros.rs @@ -1,5 +1,5 @@ use super::args::Signature; -use super::error::ParseError; +use super::error::{EmptyRangeHardfork, OverlappingRangeHardfork, ParseError}; use super::expression; use super::parser::Rule; use crate::ast::Node; @@ -7,15 +7,17 @@ use crate::ops::{ AbstractOp, Expression, ExpressionMacroDefinition, ExpressionMacroInvocation, InstructionMacroDefinition, InstructionMacroInvocation, }; +use crate::parse::error::{ExceededRangeHardfork, InvalidHardfork, InvalidRangeHardfork}; +use etk_ops::{HardFork, HardForkDirective, OperatorDirective}; use pest::iterators::Pair; use std::path::PathBuf; -pub(crate) fn parse(pair: Pair) -> Result { +pub(crate) fn parse(pair: Pair, hardfork: HardFork) -> Result { let mut pairs = pair.into_inner(); let pair = pairs.next().unwrap(); match pair.as_rule() { - Rule::instruction_macro_definition => parse_instruction_macro_defn(pair), + Rule::instruction_macro_definition => parse_instruction_macro_defn(pair, hardfork), Rule::instruction_macro => parse_instruction_macro(pair), Rule::expression_macro_definition => parse_expression_macro_defn(pair), _ => unreachable!(), @@ -45,13 +47,124 @@ pub(crate) fn parse_builtin(pair: Pair) -> Result { let expr = expression::parse(pair.into_inner().next().unwrap())?; Node::Op(AbstractOp::Push(expr.into())) } + Rule::hardfork => { + let mut directives = Vec::new(); + for inner in pair.into_inner() { + let mut directive = inner.into_inner(); + let operator = + match directive + .peek() + .and_then(|operator| match operator.as_rule() { + Rule::lt => Some(OperatorDirective::LessThan), + Rule::lte => Some(OperatorDirective::LessThanOrEqual), + Rule::gt => Some(OperatorDirective::GreaterThan), + Rule::gte => Some(OperatorDirective::GreaterThanOrEqual), + _ => None, + }) { + Some(op) => { + directive.next(); + Some(op) + } + None => None, + }; + + // Tried moving this to into() but can't manage invalid hardforks. + let hardforkstr = directive.next().unwrap().as_str(); + let hardfork = match hardforkstr { + "london" => HardFork::London, + "shanghai" => HardFork::Shanghai, + "cancun" => HardFork::Cancun, + _ => { + return InvalidHardfork { + hardfork: hardforkstr, + } + .fail() + } + }; + + directives.push(HardForkDirective { operator, hardfork }); + if directives.len() > 2 { + return ExceededRangeHardfork { + parsed: directives.len(), + } + .fail(); + } + } + + directives.reverse(); + hardfork_in_valid_range(&directives)?; + let tuple = (directives.pop().unwrap(), directives.pop()); + Node::HardforkMacro(tuple) + } _ => unreachable!(), }; Ok(node) } -fn parse_instruction_macro_defn(pair: Pair) -> Result { +fn hardfork_in_valid_range(directives: &[HardForkDirective]) -> Result<(), ParseError> { + if directives.len() == 1 { + return Ok(()); + } + + let mut decresing_bound: Option = None; + let mut incresing_bound: Option = None; + + for directive in directives { + match directive.operator { + Some(OperatorDirective::LessThan) | Some(OperatorDirective::LessThanOrEqual) => { + match decresing_bound { + Some(_) => { + return OverlappingRangeHardfork { + directive0: directives.first(), + directive1: directives.get(1), + } + .fail(); + } + None => { + decresing_bound = Some(directive.clone()); + } + } + } + Some(OperatorDirective::GreaterThan) | Some(OperatorDirective::GreaterThanOrEqual) => { + match incresing_bound { + Some(_) => { + return OverlappingRangeHardfork { + directive0: directives.first(), + directive1: directives.get(1), + } + .fail(); + } + None => { + incresing_bound = Some(directive.clone()); + } + } + } + None => { + return InvalidRangeHardfork { + directive0: directives.first(), + directive1: directives.get(1), + } + .fail(); + } + } + } + + if incresing_bound.unwrap().hardfork > decresing_bound.unwrap().hardfork { + return EmptyRangeHardfork { + directive0: directives.first(), + directive1: directives.get(1), + } + .fail(); + } + + Ok(()) +} + +fn parse_instruction_macro_defn( + pair: Pair, + hardfork: HardFork, +) -> Result { let mut pairs = pair.into_inner(); let mut macro_defn = pairs.next().unwrap().into_inner(); @@ -68,7 +181,7 @@ fn parse_instruction_macro_defn(pair: Pair) -> Result Result, ParseError> { +pub(crate) fn parse_asm(asm: &str, hardfork: HardFork) -> Result, ParseError> { let mut program: Vec = Vec::new(); let pairs = AsmParser::parse(Rule::program, asm)?; @@ -34,7 +38,7 @@ pub(crate) fn parse_asm(asm: &str) -> Result, ParseError> { let node = match pair.as_rule() { Rule::builtin => macros::parse_builtin(pair)?, Rule::EOI => continue, - _ => parse_abstract_op(pair)?.into(), + _ => parse_abstract_op(pair, hardfork.clone())?.into(), }; program.push(node); } @@ -42,16 +46,26 @@ pub(crate) fn parse_asm(asm: &str) -> Result, ParseError> { Ok(program) } -fn parse_abstract_op(pair: Pair) -> Result { +fn parse_abstract_op(pair: Pair, hardfork: HardFork) -> Result { let ret = match pair.as_rule() { - Rule::local_macro => macros::parse(pair)?, + Rule::local_macro => macros::parse(pair, hardfork)?, Rule::label_definition => { AbstractOp::Label(pair.into_inner().next().unwrap().as_str().to_string()) } - Rule::push => parse_push(pair)?, + Rule::push => parse_push(pair, hardfork)?, Rule::op => { - let spec: Op<()> = pair.as_str().parse().unwrap(); - let op = Op::new(spec).unwrap(); + let op: HardForkOp<_> = match hardfork { + HardFork::Cancun => { + HardForkOp::Cancun(CancunOp::new(pair.as_str().parse().unwrap()).unwrap()) + } + HardFork::Shanghai => { + HardForkOp::Shanghai(ShanghaiOp::new(pair.as_str().parse().unwrap()).unwrap()) + } + HardFork::London => { + HardForkOp::London(LondonOp::new(pair.as_str().parse().unwrap()).unwrap()) + } + }; + AbstractOp::Op(op) } _ => unreachable!(), @@ -60,13 +74,18 @@ fn parse_abstract_op(pair: Pair) -> Result { Ok(ret) } -fn parse_push(pair: Pair) -> Result { +fn parse_push(pair: Pair, hardfork: HardFork) -> Result { let mut pair = pair.into_inner(); let size = pair.next().unwrap(); let size: usize = size.as_str().parse().unwrap(); let operand = pair.next().unwrap(); - let spec = Op::<()>::push(size).unwrap(); + let spec = match hardfork { + HardFork::Cancun => HardForkOp::Cancun(CancunOp::<()>::push(size).unwrap()), + HardFork::Shanghai => HardForkOp::Shanghai(ShanghaiOp::<()>::push(size).unwrap()), + HardFork::London => HardForkOp::London(LondonOp::<()>::push(size).unwrap()), + }; + let expr = expression::parse(operand)?; if let Ok(val) = expr.eval() { @@ -114,7 +133,7 @@ mod tests { Op::from(Xor), Op::from(Push0) ]; - assert_matches!(parse_asm(asm), Ok(e) if e == expected); + assert_matches!(parse_asm(asm, HardFork::Cancun), Ok(e) if e == expected); } #[test] @@ -126,7 +145,7 @@ mod tests { Op::from(Push1(Imm::from([0]))), Op::from(Push1(Imm::from([1]))) ]; - assert_matches!(parse_asm(asm), Ok(e) if e == expected); + assert_matches!(parse_asm(asm, HardFork::Cancun), Ok(e) if e == expected); } #[test] @@ -140,7 +159,7 @@ mod tests { Op::from(Push1(Imm::from([1]))), Op::from(Push1(Imm::from([1]))) ]; - assert_matches!(parse_asm(asm), Ok(e) if e == expected); + assert_matches!(parse_asm(asm, HardFork::Cancun), Ok(e) if e == expected); } #[test] @@ -154,7 +173,7 @@ mod tests { Op::from(Push1(Imm::from([0]))), Op::from(Push1(Imm::from([1]))) ]; - assert_matches!(parse_asm(asm), Ok(e) if e == expected); + assert_matches!(parse_asm(asm, HardFork::Cancun), Ok(e) if e == expected); } #[test] @@ -170,8 +189,8 @@ mod tests { Op::from(Push1(Imm::from([7]))), Op::from(Push2(Imm::from([1, 0]))), ]; - println!("{:?}\n\n{:?}", parse_asm(asm), expected); - assert_matches!(parse_asm(asm), Ok(e) if e == expected); + println!("{:?}\n\n{:?}", parse_asm(asm, HardFork::Cancun), expected); + assert_matches!(parse_asm(asm, HardFork::Cancun), Ok(e) if e == expected); } #[test] @@ -197,10 +216,13 @@ mod tests { Op::from(Push2(Imm::from(hex!("0100")))), Op::from(Push4(Imm::from(hex!("ffffffff")))), ]; - assert_matches!(parse_asm(asm), Ok(e) if e == expected); + assert_matches!(parse_asm(asm, HardFork::Cancun), Ok(e) if e == expected); let asm = "push1 256"; - assert_matches!(parse_asm(asm), Err(ParseError::ImmediateTooLarge { .. })); + assert_matches!( + parse_asm(asm, HardFork::Cancun), + Err(ParseError::ImmediateTooLarge { .. }) + ); } #[test] @@ -229,10 +251,13 @@ mod tests { "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20" )))), ]; - assert_matches!(parse_asm(asm), Ok(e) if e == expected); + assert_matches!(parse_asm(asm, HardFork::Cancun), Ok(e) if e == expected); let asm = "push2 0x010203"; - assert_matches!(parse_asm(asm), Err(ParseError::ImmediateTooLarge { .. })); + assert_matches!( + parse_asm(asm, HardFork::Cancun), + Err(ParseError::ImmediateTooLarge { .. }) + ); } #[test] @@ -257,21 +282,21 @@ mod tests { Op::from(Log0), Op::from(Log4), ]; - assert_matches!(parse_asm(asm), Ok(e) if e == expected); + assert_matches!(parse_asm(asm, HardFork::Cancun), Ok(e) if e == expected); } #[test] fn parse_jumpdest_no_label() { let asm = "jumpdest"; let expected = nodes![Op::from(JumpDest)]; - assert_matches!(parse_asm(asm), Ok(e) if e == expected); + assert_matches!(parse_asm(asm, HardFork::Cancun), Ok(e) if e == expected); } #[test] fn parse_jumpdest_label() { let asm = "start:\njumpdest"; let expected = nodes![AbstractOp::Label("start".into()), Op::from(JumpDest),]; - assert_matches!(parse_asm(asm), Ok(e) if e == expected); + assert_matches!(parse_asm(asm, HardFork::Cancun), Ok(e) if e == expected); } #[test] @@ -284,7 +309,7 @@ mod tests { Op::from(Push2(Imm::with_label("snake_case"))), Op::from(JumpI) ]; - assert_matches!(parse_asm(asm), Ok(e) if e == expected); + assert_matches!(parse_asm(asm, HardFork::Cancun), Ok(e) if e == expected); } #[test] @@ -299,7 +324,7 @@ mod tests { Op::from(Push1(Imm::with_label("push1"))), Op::from(JumpI), ]; - assert_matches!(parse_asm(asm), Ok(e) if e == expected); + assert_matches!(parse_asm(asm, HardFork::Cancun), Ok(e) if e == expected); } #[test] @@ -320,8 +345,8 @@ mod tests { "a9059cbb2ab09eb219583f4a59a5d0623ade346d962bcd4e46b11da047c9049b" )))), ]; - println!("{:?}\n\n{:?}", parse_asm(asm), expected); - assert_matches!(parse_asm(asm), Ok(e) if e == expected); + println!("{:?}\n\n{:?}", parse_asm(asm, HardFork::Cancun), expected); + assert_matches!(parse_asm(asm, HardFork::Cancun), Ok(e) if e == expected); } #[test] @@ -329,7 +354,10 @@ mod tests { let asm = r#" push4 selector("name( )") "#; - assert_matches!(parse_asm(asm), Err(ParseError::Lexer { .. })); + assert_matches!( + parse_asm(asm, HardFork::Cancun), + Err(ParseError::Lexer { .. }) + ); } #[test] @@ -346,7 +374,7 @@ mod tests { Node::Include(PathBuf::from("foo.asm")), Op::from(Push1(Imm::from(2u8))), ]; - assert_matches!(parse_asm(&asm), Ok(e) if e == expected) + assert_matches!(parse_asm(&asm, HardFork::Cancun), Ok(e) if e == expected) } #[test] @@ -363,7 +391,7 @@ mod tests { Node::IncludeHex(PathBuf::from("foo.hex")), Op::from(Push1(Imm::from(2u8))), ]; - assert_matches!(parse_asm(&asm), Ok(e) if e == expected) + assert_matches!(parse_asm(&asm, HardFork::Cancun), Ok(e) if e == expected) } #[test] @@ -380,7 +408,7 @@ mod tests { Node::Import(PathBuf::from("foo.asm")), Op::from(Push1(Imm::from(2u8))), ]; - assert_matches!(parse_asm(&asm), Ok(e) if e == expected) + assert_matches!(parse_asm(&asm, HardFork::Cancun), Ok(e) if e == expected) } #[test] @@ -391,7 +419,7 @@ mod tests { "#, ); assert!(matches!( - parse_asm(&asm), + parse_asm(&asm, HardFork::Cancun), Err(ParseError::ExtraArgument { expected: 1, backtrace: _ @@ -407,7 +435,7 @@ mod tests { "#, ); assert!(matches!( - parse_asm(&asm), + parse_asm(&asm, HardFork::Cancun), Err(ParseError::MissingArgument { got: 0, expected: 1, @@ -423,7 +451,10 @@ mod tests { %import(0x44) "#, ); - assert_matches!(parse_asm(&asm), Err(ParseError::ArgumentType { .. })) + assert_matches!( + parse_asm(&asm, HardFork::Cancun), + Err(ParseError::ArgumentType { .. }) + ) } #[test] @@ -440,7 +471,7 @@ mod tests { Node::Import(PathBuf::from("hello.asm")), Op::from(Push1(Imm::from(2u8))), ]; - assert_matches!(parse_asm(&asm), Ok(e) if e == expected) + assert_matches!(parse_asm(&asm, HardFork::Cancun), Ok(e) if e == expected) } #[test] @@ -457,7 +488,7 @@ mod tests { AbstractOp::Push(Imm::with_label("hello")), Op::from(Push1(Imm::from(2u8))), ]; - assert_matches!(parse_asm(&asm), Ok(e) if e == expected) + assert_matches!(parse_asm(&asm, HardFork::Cancun), Ok(e) if e == expected) } #[test] @@ -480,12 +511,15 @@ mod tests { name: "my_macro".into(), parameters: vec!["foo".into(), "bar".into()], contents: vec![ - AbstractOp::new(GasPrice), - AbstractOp::new(Pop), - AbstractOp::new(Push1( - Expression::Plus( - Terminal::Variable("foo".to_string()).into(), - Terminal::Variable("bar".to_string()).into() + AbstractOp::new(HardForkOp::Cancun(GasPrice.into())), + AbstractOp::new(HardForkOp::Cancun(Pop.into())), + AbstractOp::new(HardForkOp::Cancun( + Push1( + Expression::Plus( + Terminal::Variable("foo".to_string()).into(), + Terminal::Variable("bar".to_string()).into() + ) + .into() ) .into() )), @@ -511,7 +545,7 @@ mod tests { }) ]; - assert_eq!(parse_asm(&asm).unwrap(), expected) + assert_eq!(parse_asm(&asm, HardFork::Cancun).unwrap(), expected) } #[test] @@ -554,7 +588,7 @@ mod tests { 2.into() )))) ]; - assert_eq!(parse_asm(&asm).unwrap(), expected) + assert_eq!(parse_asm(&asm, HardFork::Cancun).unwrap(), expected) } #[test] @@ -571,7 +605,7 @@ mod tests { AbstractOp::Push(Imm::with_expression(Expression::Plus(1.into(), 1.into()))), Op::from(Push1(Imm::from(2u8))), ]; - assert_matches!(parse_asm(&asm), Ok(e) if e == expected) + assert_matches!(parse_asm(&asm, HardFork::Cancun), Ok(e) if e == expected) } #[test] @@ -595,6 +629,6 @@ mod tests { parameters: vec![] }))), ]; - assert_eq!(parse_asm(&asm).unwrap(), expected); + assert_eq!(parse_asm(&asm, HardFork::Cancun).unwrap(), expected); } } diff --git a/etk-asm/tests/asm.rs b/etk-asm/tests/asm.rs index 8ef77aa..64fa9e9 100644 --- a/etk-asm/tests/asm.rs +++ b/etk-asm/tests/asm.rs @@ -2,6 +2,7 @@ use assert_matches::assert_matches; use etk_asm::ingest::{Error, Ingest}; +use etk_ops::HardFork; use hex_literal::hex; use std::path::{Path, PathBuf}; @@ -20,7 +21,7 @@ where #[test] fn simple_constructor() -> Result<(), Error> { let mut output = Vec::new(); - let mut ingester = Ingest::new(&mut output); + let mut ingester = Ingest::new(&mut output, HardFork::Cancun); ingester.ingest_file(source(&["simple-constructor", "ctor.etk"]))?; assert_eq!( @@ -39,7 +40,7 @@ fn simple_constructor() -> Result<(), Error> { #[test] fn out_of_bounds() { let mut output = Vec::new(); - let mut ingester = Ingest::new(&mut output); + let mut ingester = Ingest::new(&mut output, HardFork::Cancun); let err = ingester .ingest_file(source(&["out-of-bounds", "main", "main.etk"])) .unwrap_err(); @@ -50,7 +51,7 @@ fn out_of_bounds() { #[test] fn subdirectory() -> Result<(), Error> { let mut output = Vec::new(); - let mut ingester = Ingest::new(&mut output); + let mut ingester = Ingest::new(&mut output, HardFork::Cancun); ingester.ingest_file(source(&["subdirectory", "main.etk"]))?; assert_eq!(output, hex!("63c001c0de60ff")); @@ -61,7 +62,7 @@ fn subdirectory() -> Result<(), Error> { #[test] fn variable_jump() -> Result<(), Error> { let mut output = Vec::new(); - let mut ingester = Ingest::new(&mut output); + let mut ingester = Ingest::new(&mut output, HardFork::Cancun); ingester.ingest_file(source(&["variable-jump", "main.etk"]))?; assert_eq!(output, hex!("6003565b")); @@ -72,7 +73,7 @@ fn variable_jump() -> Result<(), Error> { #[test] fn instruction_macro() -> Result<(), Error> { let mut output = Vec::new(); - let mut ingester = Ingest::new(&mut output); + let mut ingester = Ingest::new(&mut output, HardFork::Cancun); ingester.ingest_file(source(&["instruction-macro", "main.etk"]))?; assert_eq!( @@ -86,7 +87,7 @@ fn instruction_macro() -> Result<(), Error> { #[test] fn instruction_macro_with_empty_lines() -> Result<(), Error> { let mut output = Vec::new(); - let mut ingester = Ingest::new(&mut output); + let mut ingester = Ingest::new(&mut output, HardFork::Cancun); ingester.ingest_file(source(&["instruction-macro", "empty_lines.etk"]))?; assert_eq!(output, hex!("6000600060006000600060006000")); @@ -97,7 +98,7 @@ fn instruction_macro_with_empty_lines() -> Result<(), Error> { #[test] fn instruction_macro_with_two_instructions_per_line() { let mut output = Vec::new(); - let mut ingester = Ingest::new(&mut output); + let mut ingester = Ingest::new(&mut output, HardFork::Cancun); let err = ingester .ingest_file(source(&[ "instruction-macro", @@ -111,7 +112,7 @@ fn instruction_macro_with_two_instructions_per_line() { #[test] fn undefined_label_undefined_macro() { let mut output = Vec::new(); - let mut ingester = Ingest::new(&mut output); + let mut ingester = Ingest::new(&mut output, HardFork::Cancun); let err = ingester .ingest_file(source(&[ "instruction-macro", @@ -127,7 +128,7 @@ fn undefined_label_undefined_macro() { #[test] fn every_op() -> Result<(), Error> { let mut output = Vec::new(); - let mut ingester = Ingest::new(&mut output); + let mut ingester = Ingest::new(&mut output, HardFork::Cancun); ingester.ingest_file(source(&["every-op", "main.etk"]))?; assert_eq!( @@ -294,9 +295,9 @@ fn every_op() -> Result<(), Error> { } #[test] -fn test_variable_sized_push_and_include() -> Result<(), Error> { +fn test_dynamic_push_and_include() -> Result<(), Error> { let mut output = Vec::new(); - let mut ingester = Ingest::new(&mut output); + let mut ingester = Ingest::new(&mut output, HardFork::Cancun); ingester.ingest_file(source(&["variable-push", "main.etk"]))?; assert_eq!(output, hex!("61025758585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585801010101010158585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858015b")); @@ -305,19 +306,61 @@ fn test_variable_sized_push_and_include() -> Result<(), Error> { } #[test] -fn test_variable_sized_push2() -> Result<(), Error> { +fn test_valid_hardfork() -> Result<(), Error> { let mut output = Vec::new(); - let mut ingester = Ingest::new(&mut output); + let mut ingester = Ingest::new(&mut output, HardFork::Cancun); + ingester.ingest_file(source(&["hardfork", "valid-hardfork.etk"]))?; + + assert_eq!(output, hex!("")); + + Ok(()) +} + +#[test] +fn test_invalid_hardfork() { + let mut output = Vec::new(); + let mut ingester = Ingest::new(&mut output, HardFork::Cancun); + let err = ingester + .ingest_file(source(&["hardfork", "invalid-hardfork.etk"])) + .unwrap_err(); + + println!("{:?}", err); + + assert_matches!(err, etk_asm::ingest::Error::Parse { source: + etk_asm::ParseError::InvalidHardfork { hardfork, .. }, .. } + if hardfork == "buenosaires".to_string()); +} + +#[test] +fn test_invalid_range_hardfork() { + let mut output = Vec::new(); + let mut ingester = Ingest::new(&mut output, HardFork::Cancun); + let err = ingester + .ingest_file(source(&["hardfork", "invalid-range.etk"])) + .unwrap_err(); + + assert_matches!( + err, + etk_asm::ingest::Error::Parse { + source: etk_asm::ParseError::EmptyRangeHardfork { .. }, + .. + } + ); +} +#[test] +fn test_dynamic_push2() -> Result<(), Error> { + let mut output = Vec::new(); + let mut ingester = Ingest::new(&mut output, HardFork::Cancun); ingester.ingest_file(source(&["variable-push2", "main1.etk"]))?; assert_eq!(output, hex!("61010158")); let mut output = Vec::new(); - let mut ingester = Ingest::new(&mut output); + let mut ingester = Ingest::new(&mut output, HardFork::Cancun); ingester.ingest_file(source(&["variable-push2", "main2.etk"]))?; assert_eq!(output, hex!("61010158")); let mut output = Vec::new(); - let mut ingester = Ingest::new(&mut output); + let mut ingester = Ingest::new(&mut output, HardFork::Cancun); ingester.ingest_file(source(&["variable-push2", "main3.etk"]))?; assert_eq!(output, hex!("610107015801")); diff --git a/etk-asm/tests/asm/hardfork/invalid-hardfork.etk b/etk-asm/tests/asm/hardfork/invalid-hardfork.etk new file mode 100644 index 0000000..8eb4da4 --- /dev/null +++ b/etk-asm/tests/asm/hardfork/invalid-hardfork.etk @@ -0,0 +1 @@ +%hardfork(">=buenosaires") \ No newline at end of file diff --git a/etk-asm/tests/asm/hardfork/invalid-range.etk b/etk-asm/tests/asm/hardfork/invalid-range.etk new file mode 100644 index 0000000..e15de07 --- /dev/null +++ b/etk-asm/tests/asm/hardfork/invalid-range.etk @@ -0,0 +1 @@ +%hardfork(">=cancun,<=london") \ No newline at end of file diff --git a/etk-asm/tests/asm/hardfork/valid-hardfork.etk b/etk-asm/tests/asm/hardfork/valid-hardfork.etk new file mode 100644 index 0000000..6579c11 --- /dev/null +++ b/etk-asm/tests/asm/hardfork/valid-hardfork.etk @@ -0,0 +1,5 @@ +%hardfork(">london,<=cancun") +%hardfork(">=cancun,<=cancun") +%hardfork("cancun") +%hardfork(">london") +%hardfork("<=cancun") \ No newline at end of file diff --git a/etk-asm/tests/asm/variable-push/included.etk b/etk-asm/tests/asm/variable-push/included.etk index db4c91e..c55517a 100644 --- a/etk-asm/tests/asm/variable-push/included.etk +++ b/etk-asm/tests/asm/variable-push/included.etk @@ -591,4 +591,4 @@ pc pc pc pc -add \ No newline at end of file +add diff --git a/etk-asm/tests/asm/variable-push/main.etk b/etk-asm/tests/asm/variable-push/main.etk index 08144f7..169a0d6 100644 --- a/etk-asm/tests/asm/variable-push/main.etk +++ b/etk-asm/tests/asm/variable-push/main.etk @@ -3,4 +3,4 @@ pc pc %include("included.etk") label: -jumpdest \ No newline at end of file +jumpdest diff --git a/etk-dasm/src/bin/disease/selectors.rs b/etk-dasm/src/bin/disease/selectors.rs index ad21a6c..280eb5d 100644 --- a/etk-dasm/src/bin/disease/selectors.rs +++ b/etk-dasm/src/bin/disease/selectors.rs @@ -1,6 +1,7 @@ use etk_4byte::reverse_selector; -use etk_ops::cancun::{Op, Operation}; +use etk_ops::cancun::Op; +use etk_ops::Operation; use std::fmt; diff --git a/etk-dasm/src/blocks/annotated.rs b/etk-dasm/src/blocks/annotated.rs index 368cd20..e1f2b76 100644 --- a/etk-dasm/src/blocks/annotated.rs +++ b/etk-dasm/src/blocks/annotated.rs @@ -3,6 +3,7 @@ use crate::sym::{Expr, Var}; use etk_ops::cancun::*; +use etk_ops::Operation; use std::collections::VecDeque; diff --git a/etk-dasm/src/blocks/basic.rs b/etk-dasm/src/blocks/basic.rs index ee0b2ab..7046380 100644 --- a/etk-dasm/src/blocks/basic.rs +++ b/etk-dasm/src/blocks/basic.rs @@ -1,7 +1,8 @@ //! A list of EVM instructions with a single point of entry and a single exit. use etk_asm::disasm::Offset; -use etk_ops::cancun::{Op, Operation}; +use etk_ops::cancun::Op; +use etk_ops::Operation; /// A list of EVM instructions with a single point of entry and a single exit. #[derive(Debug, Eq, PartialEq)] diff --git a/etk-ops/build.rs b/etk-ops/build.rs index 9769997..b21cde3 100644 --- a/etk-ops/build.rs +++ b/etk-ops/build.rs @@ -96,64 +96,7 @@ fn read_fork(name: &str) -> Result<[(String, Op); 256], Error> { fn generate_fork(fork_name: &str) -> Result<(), Error> { let ops = read_fork(fork_name)?; - let mut tokens = quote! { - /// Trait for types that represent an EVM instruction. - pub trait Operation { - /// The return type of [`Operation::code`]. - type Code: Operation + Into; - - /// The return root type of [`Operation::immediate_mut`] and - /// [`Operation::immediate`]. - type ImmediateRef: ?Sized; - - /// The type of the immediate argument for this operation. - type Immediate: - std::borrow::Borrow + std::borrow::BorrowMut; - - /// Get a shared reference to the immediate argument of this operation, - /// if one exists. - fn immediate(&self) -> Option<&Self::ImmediateRef>; - - /// Get a mutable reference to the immediate argument of this operation, - /// if one exists. - fn immediate_mut(&mut self) -> Option<&mut Self::ImmediateRef>; - - /// Consume this operation and return its immediate argument, if one - /// exists. - fn into_immediate(self) -> Option; - - /// Length of immediate argument. - fn extra_len(&self) -> usize; - - /// The action (opcode) of this operation, without any immediates. - fn code(&self) -> Self::Code; - - /// The byte (opcode) that indicates this operation. - fn code_byte(&self) -> u8 { - self.code().into() - } - - /// Human-readable name for this operation. - fn mnemonic(&self) -> &str; - - /// Returns true if the current instruction changes the program counter (other - /// than incrementing it.) - fn is_jump(&self) -> bool; - - /// Returns true if the current instruction is a valid destination for jumps. - fn is_jump_target(&self) -> bool; - - /// Returns true if the current instruction causes the EVM to stop executing - /// the contract. - fn is_exit(&self) -> bool; - - /// How many stack elements this instruction pops. - fn pops(&self) -> usize; - - /// How many stack elements this instruction pushes. - fn pushes(&self) -> usize; - } - }; + let mut tokens = quote! {use super::Operation;}; let mut code_matches = quote! {}; let mut size_matches = quote! {}; @@ -366,7 +309,6 @@ fn generate_fork(fork_name: &str) -> Result<(), Error> { }); } - let mut debug_bound = quote! {}; let mut clone_bound = quote! {}; let mut partial_eq_bound = quote! {}; let mut eq_bound = quote! {}; @@ -378,10 +320,6 @@ fn generate_fork(fork_name: &str) -> Result<(), Error> { for ii in 1..=32usize { let ident = format_ident!("P{}", ii); - debug_bound.extend(quote! { - T::#ident: std::fmt::Debug, - }); - clone_bound.extend(quote! { T::#ident: Clone, }); @@ -409,7 +347,6 @@ fn generate_fork(fork_name: &str) -> Result<(), Error> { bounds.push(quote! { #ident }); } - let debug_bound = debug_bound.to_string(); let clone_bound = clone_bound.to_string(); let partial_eq_bound = partial_eq_bound.to_string(); let eq_bound = eq_bound.to_string(); @@ -422,7 +359,6 @@ fn generate_fork(fork_name: &str) -> Result<(), Error> { #[derive(educe::Educe)] #[educe( Clone(bound = #clone_bound), - Debug(bound = #debug_bound), PartialEq(bound = #partial_eq_bound), Eq(bound = #eq_bound), Ord(bound = #ord_bound), @@ -441,6 +377,16 @@ fn generate_fork(fork_name: &str) -> Result<(), Error> { { } + // TODO: For some reason deriving Debug with educe didn't work. + impl std::fmt::Debug for Op + where + T: super::Immediates + ?Sized, + { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", self.mnemonic()) + } + } + impl Operation for Op where T: super::Immediates + ?Sized { type Code = Op<()>; type Immediate = T::Immediate;