Repository: gakonst/ethers-rs
Pull request #1049 (2022-03-17 09:25:34)

feat(solc): add viaIR option
Adds `viaIR` optionto `Settings`, stabalized in 0.8.13 


Ref https://docs.soliditylang.org/en/latest/using-the-compiler.html#input-description


Patch:
diff --git a/ethers-core/src/types/block.rs b/ethers-core/src/types/block.rs
index 61c266fc..a0ef2b5a 100644
--- a/ethers-core/src/types/block.rs
+++ b/ethers-core/src/types/block.rs
@@ -1,5 +1,7 @@
 // Taken from https://github.com/tomusdrw/rust-web3/blob/master/src/types/block.rs
 use crate::types::{Address, Bloom, Bytes, H256, U256, U64};
+#[cfg(not(feature = "celo"))]
+use core::cmp::Ordering;
 use serde::{ser::SerializeStruct, Deserialize, Deserializer, Serialize, Serializer};
 use std::str::FromStr;
 
@@ -89,6 +91,52 @@ pub struct Block<TX> {
     pub epoch_snark_data: Option<EpochSnarkData>,
 }
 
+// ref https://eips.ethereum.org/EIPS/eip-1559
+#[cfg(not(feature = "celo"))]
+pub const ELASTICITY_MULTIPLIER: U256 = U256([2u64, 0, 0, 0]);
+// max base fee delta is 12.5%
+#[cfg(not(feature = "celo"))]
+pub const BASE_FEE_MAX_CHANGE_DENOMINATOR: U256 = U256([8u64, 0, 0, 0]);
+
+impl<TX> Block<TX> {
+    /// The target gas usage as per EIP-1559
+    #[cfg(not(feature = "celo"))]
+    pub fn gas_target(&self) -> U256 {
+        self.gas_limit / ELASTICITY_MULTIPLIER
+    }
+
+    /// The next block's base fee, it is a function of parent block's base fee and gas usage.
+    /// Reference: https://eips.ethereum.org/EIPS/eip-1559
+    #[cfg(not(feature = "celo"))]
+    pub fn next_block_base_fee(&self) -> Option<U256> {
+        let target_usage = self.gas_target();
+        let base_fee_per_gas = self.base_fee_per_gas?;
+
+        match self.gas_used.cmp(&target_usage) {
+            Ordering::Greater => {
+                let gas_used_delta = self.gas_used - self.gas_target();
+                let base_fee_per_gas_delta = U256::max(
+                    base_fee_per_gas * gas_used_delta /
+                        target_usage /
+                        BASE_FEE_MAX_CHANGE_DENOMINATOR,
+                    U256::from(1u32),
+                );
+                let expected_base_fee_per_gas = base_fee_per_gas + base_fee_per_gas_delta;
+                Some(expected_base_fee_per_gas)
+            }
+            Ordering::Less => {
+                let gas_used_delta = self.gas_target() - self.gas_used;
+                let base_fee_per_gas_delta = base_fee_per_gas * gas_used_delta /
+                    target_usage /
+                    BASE_FEE_MAX_CHANGE_DENOMINATOR;
+                let expected_base_fee_per_gas = base_fee_per_gas - base_fee_per_gas_delta;
+                Some(expected_base_fee_per_gas)
+            }
+            Ordering::Equal => self.base_fee_per_gas,
+        }
+    }
+}
+
 #[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)]
 #[cfg(feature = "celo")]
 /// Commit-reveal data for generating randomness in the
@@ -279,6 +327,33 @@ mod tests {
         let block: Block<()> = serde_json::from_value(json).unwrap();
         assert_eq!(block.base_fee_per_gas, Some(U256::from(7)));
     }
+
+    #[test]
+    fn test_next_block_base_fee() {
+        // https://etherscan.io/block/14402566
+        let mut block_14402566 = Block::<TxHash>::default();
+        block_14402566.number = Some(U64::from(14402566u64));
+        block_14402566.base_fee_per_gas = Some(U256::from(36_803_013_756u128));
+        block_14402566.gas_limit = U256::from(30_087_887u128);
+        block_14402566.gas_used = U256::from(2_023_848u128);
+
+        assert_eq!(block_14402566.base_fee_per_gas, Some(U256::from(36_803_013_756u128)));
+        assert_eq!(block_14402566.gas_target(), U256::from(15_043_943u128));
+        // next block decreasing base fee https://etherscan.io/block/14402567
+        assert_eq!(block_14402566.next_block_base_fee(), Some(U256::from(32_821_521_542u128)));
+
+        // https://etherscan.io/block/14402712
+        let mut block_14402712 = Block::<TxHash>::default();
+        block_14402712.number = Some(U64::from(14402712u64));
+        block_14402712.base_fee_per_gas = Some(U256::from(24_870_031_149u128));
+        block_14402712.gas_limit = U256::from(30_000_000u128);
+        block_14402712.gas_used = U256::from(29_999_374u128);
+
+        assert_eq!(block_14402712.base_fee_per_gas, Some(U256::from(24_870_031_149u128)));
+        assert_eq!(block_14402712.gas_target(), U256::from(15_000_000u128));
+        // next block increasing base fee https://etherscan.io/block/14402713
+        assert_eq!(block_14402712.next_block_base_fee(), Some(U256::from(27_978_655_303u128)));
+    }
 }
 
 #[cfg(test)]
diff --git a/ethers-solc/src/artifacts/mod.rs b/ethers-solc/src/artifacts/mod.rs
index ef9d230d..79b82dd8 100644
--- a/ethers-solc/src/artifacts/mod.rs
+++ b/ethers-solc/src/artifacts/mod.rs
@@ -158,6 +158,10 @@ pub struct Settings {
         skip_serializing_if = "Option::is_none"
     )]
     pub evm_version: Option<EvmVersion>,
+    /// Change compilation pipeline to go through the Yul intermediate representation. This is
+    /// false by default.
+    #[serde(rename = "viaIR", default, skip_serializing_if = "Option::is_none")]
+    pub via_ir: Option<bool>,
     #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
     pub libraries: BTreeMap<String, BTreeMap<String, String>>,
 }
@@ -240,6 +244,19 @@ impl Settings {
             .insert(key.into(), values.into_iter().map(|s| s.to_string()).collect());
     }
 
+    /// Sets the ``viaIR` valu
+    #[must_use]
+    pub fn set_via_ir(mut self, via_ir: bool) -> Self {
+        self.via_ir = Some(via_ir);
+        self
+    }
+
+    /// Enables `viaIR`
+    #[must_use]
+    pub fn with_via_ir(self) -> Self {
+        self.set_via_ir(true)
+    }
+
     /// Adds `ast` to output
     #[must_use]
     pub fn with_ast(mut self) -> Self {
@@ -258,6 +275,7 @@ impl Default for Settings {
             metadata: None,
             output_selection: OutputSelection::default_output_selection(),
             evm_version: Some(EvmVersion::default()),
+            via_ir: None,
             libraries: Default::default(),
             remappings: Default::default(),
         }
diff --git a/ethers-solc/src/project_util/mock.rs b/ethers-solc/src/project_util/mock.rs
index 78fb81a9..8576a520 100644
--- a/ethers-solc/src/project_util/mock.rs
+++ b/ethers-solc/src/project_util/mock.rs
@@ -12,7 +12,7 @@ use rand::{
 };
 use serde::{Deserialize, Serialize};
 use std::{
-    collections::{BTreeSet, HashMap},
+    collections::{BTreeSet, HashMap, HashSet, VecDeque},
     path::{Path, PathBuf},
 };
 
@@ -25,6 +25,13 @@ pub struct MockProjectSkeleton {
     pub libraries: Vec<MockLib>,
 }
 
+impl MockProjectSkeleton {
+    /// Returns a list of file ids the given file id imports.
+    pub fn imported_nodes(&self, from: usize) -> impl Iterator<Item = usize> + '_ {
+        self.files[from].imports.iter().map(|i| i.file_id())
+    }
+}
+
 /// Represents a virtual project
 #[derive(Serialize)]
 pub struct MockProjectGenerator {
@@ -272,11 +279,27 @@ impl MockProjectGenerator {
         }
     }
 
+    /// Returns the file for the given id
+    pub fn get_file(&self, id: usize) -> &MockFile {
+        &self.inner.files[id]
+    }
+
     /// All file ids
     pub fn file_ids(&self) -> impl Iterator<Item = usize> + '_ {
         self.inner.files.iter().map(|f| f.id)
     }
 
+    /// Returns an iterator over all file ids that are source files or imported by source files
+    ///
+    /// In other words, all files that are relevant in order to compile the project's source files.
+    pub fn used_file_ids(&self) -> impl Iterator<Item = usize> + '_ {
+        let mut file_ids = BTreeSet::new();
+        for file in self.internal_file_ids() {
+            file_ids.extend(NodesIter::new(file, &self.inner))
+        }
+        file_ids.into_iter()
+    }
+
     /// All ids of internal files
     pub fn internal_file_ids(&self) -> impl Iterator<Item = usize> + '_ {
         self.inner.files.iter().filter(|f| !f.is_external()).map(|f| f.id)
@@ -469,6 +492,15 @@ pub enum MockImport {
     External(usize, usize),
 }
 
+impl MockImport {
+    pub fn file_id(&self) -> usize {
+        *match self {
+            MockImport::Internal(id) => id,
+            MockImport::External(_, id) => id,
+        }
+    }
+}
+
 /// Container of a mock lib
 #[derive(Debug, Clone, Serialize, Deserialize)]
 pub struct MockLib {
@@ -552,6 +584,33 @@ impl Default for MockProjectSettings {
     }
 }
 
+/// An iterator over a node and its dependencies
+struct NodesIter<'a> {
+    /// stack of nodes
+    stack: VecDeque<usize>,
+    visited: HashSet<usize>,
+    skeleton: &'a MockProjectSkeleton,
+}
+
+impl<'a> NodesIter<'a> {
+    fn new(start: usize, skeleton: &'a MockProjectSkeleton) -> Self {
+        Self { stack: VecDeque::from([start]), visited: HashSet::new(), skeleton }
+    }
+}
+
+impl<'a> Iterator for NodesIter<'a> {
+    type Item = usize;
+    fn next(&mut self) -> Option<Self::Item> {
+        let file = self.stack.pop_front()?;
+
+        if self.visited.insert(file) {
+            // push the file's direct imports to the stack if we haven't visited it already
+            self.stack.extend(self.skeleton.imported_nodes(file));
+        }
+        Some(file)
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;


Test patch:
diff --git a/ethers-solc/tests/mocked.rs b/ethers-solc/tests/mocked.rs
index 4bd717b2..3fdffb97 100644
--- a/ethers-solc/tests/mocked.rs
+++ b/ethers-solc/tests/mocked.rs
@@ -3,7 +3,7 @@
 use ethers_solc::{
     error::Result,
     project_util::{
-        mock::{MockProjectGenerator, MockProjectSettings},
+        mock::{MockProjectGenerator, MockProjectSettings, MockProjectSkeleton},
         TempProject,
     },
 };
@@ -85,7 +85,7 @@ fn can_compile_mocked_modified() {
     run_mock(MockProjectSettings::random(), |project, gen| {
         project.ensure_no_errors_recompile_unchanged()?;
         // modify a random file
-        gen.modify_file(gen.file_ids().count() / 2, project.paths(), DEFAULT_VERSION)?;
+        gen.modify_file(gen.used_file_ids().count() / 2, project.paths(), DEFAULT_VERSION)?;
         project.ensure_changed()?;
         project.artifacts_snapshot()?.assert_artifacts_essentials_present();
         Ok(())
@@ -97,7 +97,7 @@ fn can_compile_mocked_modified_all() {
     run_mock(MockProjectSettings::random(), |project, gen| {
         project.ensure_no_errors_recompile_unchanged()?;
         // modify a random file
-        for id in gen.file_ids() {
+        for id in gen.used_file_ids() {
             gen.modify_file(id, project.paths(), DEFAULT_VERSION)?;
             project.ensure_changed()?;
             project.artifacts_snapshot()?.assert_artifacts_essentials_present();
@@ -105,3 +105,27 @@ fn can_compile_mocked_modified_all() {
         Ok(())
     });
 }
+
+// a test useful to manually debug a serialized skeleton
+#[test]
+fn can_compile_skeleton() {
+    let mut project = TempProject::dapptools().unwrap();
+    let s = r#"{"files":[{"id":0,"name":"SourceFile0","imports":[{"External":[0,1]},{"External":[3,4]}],"lib_id":null,"emit_artifacts":true},{"id":1,"name":"SourceFile1","imports":[],"lib_id":0,"emit_artifacts":true},{"id":2,"name":"SourceFile2","imports":[],"lib_id":1,"emit_artifacts":true},{"id":3,"name":"SourceFile3","imports":[],"lib_id":2,"emit_artifacts":true},{"id":4,"name":"SourceFile4","imports":[],"lib_id":3,"emit_artifacts":true}],"libraries":[{"name":"Lib0","id":0,"offset":1,"num_files":1},{"name":"Lib1","id":1,"offset":2,"num_files":1},{"name":"Lib2","id":2,"offset":3,"num_files":1},{"name":"Lib3","id":3,"offset":4,"num_files":1}]}"#;
+    let gen: MockProjectGenerator = serde_json::from_str::<MockProjectSkeleton>(s).unwrap().into();
+    let remappings = gen.remappings_at(project.root());
+    project.paths_mut().remappings.extend(remappings);
+    project.mock(&gen, DEFAULT_VERSION).unwrap();
+
+    // mattsse: helper to show what's being generated
+    // gen.write_to(&ethers_solc::ProjectPathsConfig::dapptools("./skeleton").unwrap(),
+    // DEFAULT_VERSION).unwrap();
+
+    let compiled = project.compile().unwrap();
+    assert!(!compiled.has_compiler_errors());
+    assert!(!compiled.is_unchanged());
+    for id in gen.used_file_ids() {
+        gen.modify_file(id, project.paths(), DEFAULT_VERSION).unwrap();
+        project.ensure_changed().unwrap();
+        project.artifacts_snapshot().unwrap().assert_artifacts_essentials_present();
+    }
+}