diff --git a/Cargo.lock b/Cargo.lock index f4c9309a..2e23e01d 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 6b35f108..9b9e7641 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 456d506c..fa195f16 100644 --- a/doc/src/ch02-lang/ch02-expressions.md +++ b/doc/src/ch02-lang/ch02-expressions.md @@ -10,11 +10,12 @@ 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]); ``` @@ -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 eb88ff86..6aba6396 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 4e19be8f..efaaff0b 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]); ``` diff --git a/doc/src/ch02-lang/ch04-macros/ch01-builtins.md b/doc/src/ch02-lang/ch04-macros/ch01-builtins.md index 05581df9..f8b1cc87 100644 --- a/doc/src/ch02-lang/ch04-macros/ch01-builtins.md +++ b/doc/src/ch02-lang/ch04-macros/ch01-builtins.md @@ -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]); ``` diff --git a/doc/src/ch02-lang/ch04-macros/ch02-expression.md b/doc/src/ch02-lang/ch04-macros/ch02-expression.md index 8be11f79..ef221384 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 07dde030..9c29e04d 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 886c1f38..34504701 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 d52cbb87..82bf9f39 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 622308e3..b41c7d0e 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, @@ -143,8 +143,10 @@ mod error { pub use self::error::Error; use crate::ops::expression::Error::{UndefinedVariable, UnknownLabel, UnknownMacro}; use crate::ops::{self, AbstractOp, Assemble, Expression, 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(()) @@ -219,6 +222,9 @@ pub struct Assembler { /// Pushes that are variable-sized and need to be backpatched. variable_sized_push: Vec, + + /// Hardfork to use when assembling. + hardfork: HardFork, } /// A label definition. @@ -244,11 +250,19 @@ impl LabelDef { } 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. @@ -320,10 +334,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()) @@ -462,10 +476,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 +593,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 +673,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 +745,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 +767,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 +787,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,8 +808,11 @@ 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); @@ -812,11 +831,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 +845,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 +860,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 +874,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 +936,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 +951,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 +970,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 +985,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 +1011,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 +1022,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 +1048,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 +1072,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 +1085,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 +1115,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 +1127,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 +1157,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 +1174,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 +1183,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 +1196,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 +1208,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 +1228,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 +1245,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 +1262,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 +1286,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 +1307,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 +1323,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 +1347,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 +1391,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 +1407,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 +1432,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 +1454,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 +1477,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 +1489,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 +1510,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 +1522,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 e3824c15..ea1278ac 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 cf3285cd..e0ceb9b5 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 0cef990d..9acfc277 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 1749459a..45fe7ce1 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 19c9f1bd..3547fa19 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 ccc89e9e..c6223564 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 c262917f..fdcaeb7e 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 8ef77aa2..64fa9e97 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 00000000..8eb4da4a --- /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 00000000..e15de07a --- /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 00000000..5050de8c --- /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") diff --git a/etk-asm/tests/asm/variable-push/included.etk b/etk-asm/tests/asm/variable-push/included.etk index db4c91e2..c55517ab 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 08144f7c..169a0d65 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 ad21a6c9..280eb5de 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 368cd20f..e1f2b761 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 ee0b2abf..70463806 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 97699974..b21cde31 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; diff --git a/etk-ops/src/lib.rs b/etk-ops/src/lib.rs index 52520016..beadc6fb 100644 --- a/etk-ops/src/lib.rs +++ b/etk-ops/src/lib.rs @@ -14,6 +14,66 @@ use snafu::{Backtrace, Snafu}; use std::borrow::{Borrow, BorrowMut}; +use std::fmt::Display; +use std::str::FromStr; + +/// 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; +} + pub mod london { //! Instructions available in the London hard fork. include!(concat!(env!("OUT_DIR"), "/london.rs")); @@ -29,6 +89,486 @@ pub mod cancun { include!(concat!(env!("OUT_DIR"), "/cancun.rs")); } +/// An operation that can be executed by the EVM. +#[derive(Debug)] +pub enum HardForkOp +where + T: Immediates + ?Sized, +{ + /// Cancun hard fork operations. + Cancun(cancun::Op), + + /// Shanghai hard fork operations. + Shanghai(shanghai::Op), + + /// London hard fork operations. + London(london::Op), +} + +impl Clone for HardForkOp +where + T: Immediates + ?Sized, + T::P1: Clone, + T::P2: Clone, + T::P3: Clone, + T::P4: Clone, + T::P5: Clone, + T::P6: Clone, + T::P7: Clone, + T::P8: Clone, + T::P9: Clone, + T::P10: Clone, + T::P11: Clone, + T::P12: Clone, + T::P13: Clone, + T::P14: Clone, + T::P15: Clone, + T::P16: Clone, + T::P17: Clone, + T::P18: Clone, + T::P19: Clone, + T::P20: Clone, + T::P21: Clone, + T::P22: Clone, + T::P23: Clone, + T::P24: Clone, + T::P25: Clone, + T::P26: Clone, + T::P27: Clone, + T::P28: Clone, + T::P29: Clone, + T::P30: Clone, + T::P31: Clone, + T::P32: Clone, +{ + fn clone(&self) -> Self { + match *self { + Self::Cancun(ref op) => Self::Cancun(op.clone()), + Self::Shanghai(ref op) => Self::Shanghai(op.clone()), + Self::London(ref op) => Self::London(op.clone()), + } + } +} + +impl PartialEq> for HardForkOp +where + T: Immediates + ?Sized, + T::P1: PartialEq, + T::P2: PartialEq, + T::P3: PartialEq, + T::P4: PartialEq, + T::P5: PartialEq, + T::P6: PartialEq, + T::P7: PartialEq, + T::P8: PartialEq, + T::P9: PartialEq, + T::P10: PartialEq, + T::P11: PartialEq, + T::P12: PartialEq, + T::P13: PartialEq, + T::P14: PartialEq, + T::P15: PartialEq, + T::P16: PartialEq, + T::P17: PartialEq, + T::P18: PartialEq, + T::P19: PartialEq, + T::P20: PartialEq, + T::P21: PartialEq, + T::P22: PartialEq, + T::P23: PartialEq, + T::P24: PartialEq, + T::P25: PartialEq, + T::P26: PartialEq, + T::P27: PartialEq, + T::P28: PartialEq, + T::P29: PartialEq, + T::P30: PartialEq, + T::P31: PartialEq, + T::P32: PartialEq, +{ + fn eq(&self, other: &HardForkOp) -> bool { + match (self, other) { + (Self::Cancun(op1), HardForkOp::Cancun(op2)) => op1 == op2, + (Self::Shanghai(op1), HardForkOp::Shanghai(op2)) => op1 == op2, + (Self::London(op1), HardForkOp::London(op2)) => op1 == op2, + _ => false, + } + } +} + +impl Eq for HardForkOp +where + T: Immediates + ?Sized, + T::P1: Eq, + T::P2: Eq, + T::P3: Eq, + T::P4: Eq, + T::P5: Eq, + T::P6: Eq, + T::P7: Eq, + T::P8: Eq, + T::P9: Eq, + T::P10: Eq, + T::P11: Eq, + T::P12: Eq, + T::P13: Eq, + T::P14: Eq, + T::P15: Eq, + T::P16: Eq, + T::P17: Eq, + T::P18: Eq, + T::P19: Eq, + T::P20: Eq, + T::P21: Eq, + T::P22: Eq, + T::P23: Eq, + T::P24: Eq, + T::P25: Eq, + T::P26: Eq, + T::P27: Eq, + T::P28: Eq, + T::P29: Eq, + T::P30: Eq, + T::P31: Eq, + T::P32: Eq, +{ +} + +impl HardForkOp +where + T: Immediates + ?Sized, +{ + /// Get an operation from a [`HardForkOp`] enum. + pub fn new_op(code: HardForkOp<()>) -> Option> { + match code { + HardForkOp::Cancun(op) => cancun::Op::new(op).map(HardForkOp::Cancun), + HardForkOp::Shanghai(op) => shanghai::Op::new(op).map(HardForkOp::Shanghai), + HardForkOp::London(op) => london::Op::new(op).map(HardForkOp::London), + } + } + + /// Returns the total length of this operation, including its immediate. + pub fn size(&self) -> usize { + match self { + HardForkOp::Cancun(op) => op.size(), + HardForkOp::Shanghai(op) => op.size(), + HardForkOp::London(op) => op.size(), + } + } +} + +impl HardForkOp<()> { + /// Join this opcode with an immediate argument. + /// + /// Panics if this opcode does not take an immediate argument. + pub fn with(self, immediate: I) -> Result, E> + where + T: ?Sized + Immediates, + I: TryInto, + I: TryInto, + I: TryInto, + I: TryInto, + I: TryInto, + I: TryInto, + I: TryInto, + I: TryInto, + I: TryInto, + I: TryInto, + I: TryInto, + I: TryInto, + I: TryInto, + I: TryInto, + I: TryInto, + I: TryInto, + I: TryInto, + I: TryInto, + I: TryInto, + I: TryInto, + I: TryInto, + I: TryInto, + I: TryInto, + I: TryInto, + I: TryInto, + I: TryInto, + I: TryInto, + I: TryInto, + I: TryInto, + I: TryInto, + I: TryInto, + I: TryInto, + { + match self { + HardForkOp::Cancun(op) => Ok(HardForkOp::Cancun(op.with(immediate)?)), + HardForkOp::Shanghai(op) => Ok(HardForkOp::Shanghai(op.with(immediate)?)), + HardForkOp::London(op) => Ok(HardForkOp::London(op.with(immediate)?)), + } + } +} + +impl Operation for HardForkOp +where + T: Immediates + ?Sized, +{ + type Code = HardForkOp<()>; + type Immediate = T::Immediate; + type ImmediateRef = T::ImmediateRef; + fn immediate(&self) -> Option<&Self::ImmediateRef> { + match self { + HardForkOp::Cancun(op) => op.immediate(), + HardForkOp::Shanghai(op) => op.immediate(), + HardForkOp::London(op) => op.immediate(), + } + } + fn immediate_mut(&mut self) -> Option<&mut Self::ImmediateRef> { + match self { + HardForkOp::Cancun(op) => op.immediate_mut(), + HardForkOp::Shanghai(op) => op.immediate_mut(), + HardForkOp::London(op) => op.immediate_mut(), + } + } + fn into_immediate(self) -> Option { + match self { + HardForkOp::Cancun(op) => op.into_immediate(), + HardForkOp::Shanghai(op) => op.into_immediate(), + HardForkOp::London(op) => op.into_immediate(), + } + } + fn extra_len(&self) -> usize { + match self { + HardForkOp::Cancun(op) => op.extra_len(), + HardForkOp::Shanghai(op) => op.extra_len(), + HardForkOp::London(op) => op.extra_len(), + } + } + fn code(&self) -> Self::Code { + match self { + HardForkOp::Cancun(op) => HardForkOp::Cancun(op.code()), + HardForkOp::Shanghai(op) => HardForkOp::Shanghai(op.code()), + HardForkOp::London(op) => HardForkOp::London(op.code()), + } + } + fn mnemonic(&self) -> &str { + match self { + HardForkOp::Cancun(op) => op.mnemonic(), + HardForkOp::Shanghai(op) => op.mnemonic(), + HardForkOp::London(op) => op.mnemonic(), + } + } + fn is_jump(&self) -> bool { + match self { + HardForkOp::Cancun(op) => op.is_jump(), + HardForkOp::Shanghai(op) => op.is_jump(), + HardForkOp::London(op) => op.is_jump(), + } + } + fn is_jump_target(&self) -> bool { + match self { + HardForkOp::Cancun(op) => op.is_jump_target(), + HardForkOp::Shanghai(op) => op.is_jump_target(), + HardForkOp::London(op) => op.is_jump_target(), + } + } + fn is_exit(&self) -> bool { + match self { + HardForkOp::Cancun(op) => op.is_exit(), + HardForkOp::Shanghai(op) => op.is_exit(), + HardForkOp::London(op) => op.is_exit(), + } + } + fn pops(&self) -> usize { + match self { + HardForkOp::Cancun(op) => op.pops(), + HardForkOp::Shanghai(op) => op.pops(), + HardForkOp::London(op) => op.pops(), + } + } + fn pushes(&self) -> usize { + match self { + HardForkOp::Cancun(op) => op.pushes(), + HardForkOp::Shanghai(op) => op.pushes(), + HardForkOp::London(op) => op.pushes(), + } + } +} + +impl From> for u8 { + fn from(op: HardForkOp<()>) -> u8 { + match op { + HardForkOp::Cancun(op) => op.code_byte(), + HardForkOp::Shanghai(op) => op.code_byte(), + HardForkOp::London(op) => op.code_byte(), + } + } +} + +impl std::fmt::Display for HardForkOp<()> { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + HardForkOp::Cancun(op) => op.fmt(f), + HardForkOp::Shanghai(op) => op.fmt(f), + HardForkOp::London(op) => op.fmt(f), + } + } +} + +/// Hard forks of the Ethereum Virtual Machine. +#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq)] +pub enum HardFork { + /// The London hard fork. + London, + + /// The Shanghai hard fork. + Shanghai, + + /// The Cancun hard fork. + Cancun, +} + +impl HardFork { + /// Returns true if the given hardfork directive is valid for this hardfork. + pub fn is_valid(&self, directive: &HardForkDirective) -> bool { + match directive.operator { + Some(OperatorDirective::GreaterThan) => self > &directive.hardfork, + Some(OperatorDirective::GreaterThanOrEqual) => self >= &directive.hardfork, + Some(OperatorDirective::LessThan) => self < &directive.hardfork, + Some(OperatorDirective::LessThanOrEqual) => self <= &directive.hardfork, + None => self == &directive.hardfork, + } + } +} + +impl Default for HardFork { + fn default() -> Self { + Self::Cancun + } +} + +impl Display for HardFork { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Self::London => write!(f, "london"), + Self::Shanghai => write!(f, "shanghai"), + Self::Cancun => write!(f, "cancun"), + } + } +} + +impl FromStr for HardFork { + type Err = FromStrError; + + fn from_str(s: &str) -> Result { + match s { + "london" => Ok(HardFork::London), + "shanghai" => Ok(HardFork::Shanghai), + "cancun" => Ok(HardFork::Cancun), + _ => FromStrSnafu { mnemonic: s }.fail(), + } + } +} + +/// A directive that specifies a hardfork. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct HardForkDirective { + /// The operator used to define a range of hardforks. + pub operator: Option, + + /// The hardfork. + pub hardfork: HardFork, +} + +impl Display for HardForkDirective { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match &self.operator { + Some(op) => write!(f, "{}{}", op, self.hardfork), + None => write!(f, "{}", self.hardfork), + } + } +} + +impl From> for HardForkDirective { + fn from(option: Option<&HardForkDirective>) -> Self { + match option { + Some(hfd) => hfd.to_owned(), + None => panic!("Cannot convert None into HardForkDirective"), + } + } +} + +impl PartialOrd> for HardForkDirective { + fn partial_cmp(&self, other: &Option) -> Option { + match other { + Some(_) => self.partial_cmp(other), + None => Some(std::cmp::Ordering::Greater), + } + } +} + +impl PartialEq> for HardForkDirective { + fn eq(&self, other: &Option) -> bool { + match other { + Some(hfd) => self.eq(hfd), + None => false, + } + } +} + +impl PartialOrd for Option { + fn partial_cmp(&self, other: &HardForkDirective) -> Option { + match self { + Some(hfd) => hfd.partial_cmp(&Some(other.to_owned())), + None => Some(std::cmp::Ordering::Less), + } + } +} + +impl PartialEq for Option { + fn eq(&self, other: &HardForkDirective) -> bool { + match self { + Some(hfd) => hfd.eq(other), + None => false, + } + } +} + +/// An operator used to define a range of hardforks. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum OperatorDirective { + /// The `>=` operator. + GreaterThan, + + /// The `<=` operator. + GreaterThanOrEqual, + + /// The `>` operator. + LessThan, + + /// The `<` operator. + LessThanOrEqual, +} + +impl From<&str> for OperatorDirective { + fn from(s: &str) -> Self { + match s { + ">=" => Self::GreaterThanOrEqual, + "<=" => Self::LessThanOrEqual, + ">" => Self::GreaterThan, + "<" => Self::LessThan, + _ => panic!("Invalid operator: {}", s), + } + } +} + +impl Display for OperatorDirective { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Self::GreaterThanOrEqual => write!(f, ">="), + Self::LessThanOrEqual => write!(f, "<="), + Self::GreaterThan => write!(f, ">"), + Self::LessThan => write!(f, "<"), + } + } +} + /// Error that can occur when parsing an operation from a string. #[derive(Debug, Snafu)] pub struct FromStrError {