Skip to content
Merged
36 changes: 36 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 10 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ publish = false
[workspace]
resolver = "3"
members = [
"crates/chainspec",
"crates/evm",
"crates/payload/builder",
"crates/payload/types",
"crates/primitives",
"crates/revm",
"crates/chainspec",
"crates/consensus",
"crates/evm",
"crates/payload/builder",
"crates/payload/types",
"crates/primitives",
"crates/revm",
]

[workspace.lints]
Expand All @@ -37,6 +38,7 @@ all = "warn"

[workspace.dependencies]
morph-chainspec = { path = "crates/chainspec", default-features = false }
morph-consensus = { path = "crates/consensus", default-features = false }
morph-evm = { path = "crates/evm", default-features = false }
morph-payload-builder = { path = "crates/payload/builder", default-features = false }
morph-payload-types = { path = "crates/payload/types", default-features = false }
Expand Down Expand Up @@ -90,6 +92,7 @@ reth-rpc-server-types = { git = "https://github.com/paradigmxyz/reth", rev = "64
reth-storage-api = { git = "https://github.com/paradigmxyz/reth", rev = "64909d3" }
reth-tracing = { git = "https://github.com/paradigmxyz/reth", rev = "64909d3" }
reth-transaction-pool = { git = "https://github.com/paradigmxyz/reth", rev = "64909d3" }
reth-zstd-compressors = { git = "https://github.com/paradigmxyz/reth", rev = "64909d3", default-features = false }

reth-revm = { git = "https://github.com/paradigmxyz/reth", rev = "64909d3", features = [
"std",
Expand Down Expand Up @@ -117,7 +120,7 @@ alloy-signer = "1.1.3"
alloy-signer-local = "1.1.3"
alloy-sol-types = "1.4.1"
alloy-transport = "1.1.3"

alloy-chains = { version = "0.2.5", default-features = false }
arbitrary = { version = "1.3", features = ["derive"] }
async-lock = "3.4.1"
async-trait = "0.1"
Expand Down
4 changes: 4 additions & 0 deletions crates/chainspec/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,24 @@ reth-cli = { workspace = true, optional = true }
reth-chainspec.workspace = true
reth-network-peers.workspace = true

alloy-chains.workspace = true
alloy-consensus.workspace = true
alloy-evm.workspace = true
alloy-genesis.workspace = true
alloy-primitives.workspace = true
alloy-eips.workspace = true
alloy-hardforks.workspace = true
alloy-serde.workspace = true

auto_impl.workspace = true
eyre = { workspace = true, optional = true }
serde.workspace = true
serde_json.workspace = true

[features]
default = ["serde"]
serde = [
"alloy-consensus/serde",
"alloy-eips/serde",
"alloy-hardforks/serde",
"alloy-primitives/serde",
Expand Down
884 changes: 884 additions & 0 deletions crates/chainspec/res/genesis/hoodi.json

Large diffs are not rendered by default.

930 changes: 930 additions & 0 deletions crates/chainspec/res/genesis/mainnet.json

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions crates/chainspec/src/constants.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//! Morph chainspec constants.

/// The Morph Mainnet chain ID.
pub const MORPH_MAINNET_CHAIN_ID: u64 = 2818;

/// The Morph Hoodi (testnet) chain ID.
pub const MORPH_HOODI_CHAIN_ID: u64 = 2910;

/// The default L2 sequencer fee (0.001 Gwei = 1_000_000 wei).
/// The sequencer has the right to set any base fee below `MORPH_MAX_BASE_FEE`.
pub const MORPH_BASE_FEE: u64 = 1_000_000;
187 changes: 187 additions & 0 deletions crates/chainspec/src/genesis.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
//! Morph types for genesis data.

use alloy_primitives::Address;
use alloy_serde::OtherFields;
use serde::{Deserialize, Serialize, de::Error as _};

/// Container type for all Morph-specific fields in a genesis file.
#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MorphGenesisInfo {
/// Information about hard forks specific to the Morph chain.
#[serde(skip_serializing_if = "Option::is_none")]
pub hard_fork_info: Option<MorphHardforkInfo>,
/// Morph chain-specific configuration details.
pub morph_chain_info: MorphChainConfig,
}

impl MorphGenesisInfo {
/// Extracts the Morph specific fields from a genesis file.
pub fn extract_from(others: &OtherFields) -> Option<Self> {
Self::try_from(others).ok()
}
}

impl TryFrom<&OtherFields> for MorphGenesisInfo {
type Error = serde_json::Error;

fn try_from(others: &OtherFields) -> Result<Self, Self::Error> {
let hard_fork_info = MorphHardforkInfo::try_from(others).ok();
let morph_chain_info = MorphChainConfig::try_from(others)?;

Comment on lines +28 to +31
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cd crates/chainspec && wc -l src/genesis.rs

Repository: morph-l2/morph-reth

Length of output: 81


🏁 Script executed:

cd crates/chainspec && sed -n '20,40p' src/genesis.rs

Repository: morph-l2/morph-reth

Length of output: 679


🏁 Script executed:

cd crates/chainspec && grep -n "MorphHardforkInfo" src/genesis.rs

Repository: morph-l2/morph-reth

Length of output: 425


🏁 Script executed:

cd crates/chainspec && grep -n "impl.*try_from" src/genesis.rs | head -20

Repository: morph-l2/morph-reth

Length of output: 45


🏁 Script executed:

cd crates/chainspec && sed -n '1,80p' src/genesis.rs

Repository: morph-l2/morph-reth

Length of output: 2860


🏁 Script executed:

cd crates/chainspec && sed -n '64,120p' src/genesis.rs

Repository: morph-l2/morph-reth

Length of output: 1945


🏁 Script executed:

cd crates/chainspec && rg "hard_fork_info" -A 2 -B 2

Repository: morph-l2/morph-reth

Length of output: 1135


Propagate deserialization errors for hardfork configuration.

When hardfork data is provided in the genesis file but malformed, .ok() silently converts the error to None, making it indistinguishable from an omitted field. This can cause data loss and incorrect fork schedules without alerting the user. Propagate errors instead so invalid hardfork config is caught during genesis parsing.

🛠️ Suggested fix
-        let hard_fork_info = MorphHardforkInfo::try_from(others).ok();
+        let hard_fork_info = Some(MorphHardforkInfo::try_from(others)?);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fn try_from(others: &OtherFields) -> Result<Self, Self::Error> {
let hard_fork_info = MorphHardforkInfo::try_from(others).ok();
let morph_chain_info = MorphChainConfig::try_from(others)?;
fn try_from(others: &OtherFields) -> Result<Self, Self::Error> {
let hard_fork_info = Some(MorphHardforkInfo::try_from(others)?);
let morph_chain_info = MorphChainConfig::try_from(others)?;
🤖 Prompt for AI Agents
In `@crates/chainspec/src/genesis.rs` around lines 28 - 31, The current try_from
in genesis.rs swallows errors from MorphHardforkInfo::try_from(others) by
calling .ok(), which hides malformed hardfork data; change that to propagate the
error instead (use the ? operator on MorphHardforkInfo::try_from(others)) so
deserialization failures surface during genesis parsing; adjust subsequent logic
that assumed Option (if any) to handle the propagated Result or Option
accordingly, referencing the try_from function, MorphHardforkInfo,
MorphChainConfig, and OtherFields to locate the code to change.

Ok(Self {
hard_fork_info,
morph_chain_info,
})
}
}

/// Morph hardfork info specifies the block numbers and timestamps at which
/// the Morph hardforks were activated.
///
/// Note: Bernoulli and Curie use block-based activation, while Morph203, Viridian,
/// and Emerald use timestamp-based activation (matching go-ethereum behavior).
#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MorphHardforkInfo {
/// Bernoulli hardfork block number.
#[serde(skip_serializing_if = "Option::is_none")]
pub bernoulli_block: Option<u64>,
/// Curie hardfork block number.
#[serde(skip_serializing_if = "Option::is_none")]
pub curie_block: Option<u64>,
/// Morph203 hardfork timestamp.
#[serde(skip_serializing_if = "Option::is_none")]
pub morph203_time: Option<u64>,
/// Viridian hardfork timestamp.
#[serde(skip_serializing_if = "Option::is_none")]
pub viridian_time: Option<u64>,
/// Emerald hardfork timestamp.
#[serde(skip_serializing_if = "Option::is_none")]
pub emerald_time: Option<u64>,
}

impl MorphHardforkInfo {
/// Extract the Morph-specific genesis info from a genesis file.
pub fn extract_from(others: &OtherFields) -> Option<Self> {
Self::try_from(others).ok()
}
}

impl TryFrom<&OtherFields> for MorphHardforkInfo {
type Error = serde_json::Error;

fn try_from(others: &OtherFields) -> Result<Self, Self::Error> {
others.deserialize_as()
}
}

/// The configuration for the Morph chain.
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MorphChainConfig {
/// The address of the L2 transaction fee vault.
#[serde(skip_serializing_if = "Option::is_none")]
pub fee_vault_address: Option<Address>,
/// The maximum tx payload size per block in bytes.
#[serde(skip_serializing_if = "Option::is_none")]
pub max_tx_payload_bytes_per_block: Option<usize>,
}

impl MorphChainConfig {
/// Extracts the morph config by looking for the `morph` key in genesis.
pub fn extract_from(others: &OtherFields) -> Option<Self> {
Self::try_from(others).ok()
}

/// Returns whether the fee vault is enabled.
pub const fn is_fee_vault_enabled(&self) -> bool {
self.fee_vault_address.is_some()
}

/// Checks if the given block size (in bytes) is valid for this chain.
pub fn is_valid_block_size(&self, size: usize) -> bool {
self.max_tx_payload_bytes_per_block
.map(|max| size <= max)
.unwrap_or(true)
}
}

impl TryFrom<&OtherFields> for MorphChainConfig {
type Error = serde_json::Error;

fn try_from(others: &OtherFields) -> Result<Self, Self::Error> {
if let Some(Ok(morph_config)) = others.get_deserialized::<Self>("morph") {
Ok(morph_config)
} else {
Err(serde_json::Error::missing_field("morph"))
}
Comment on lines +113 to +118
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, locate and examine the file
find . -name "genesis.rs" -path "*/chainspec/*" | head -5

Repository: morph-l2/morph-reth

Length of output: 96


🏁 Script executed:

# Read the specific lines mentioned in the review
sed -n '105,125p' crates/chainspec/src/genesis.rs

Repository: morph-l2/morph-reth

Length of output: 577


🏁 Script executed:

# Get more context around the try_from implementation
sed -n '100,150p' crates/chainspec/src/genesis.rs

Repository: morph-l2/morph-reth

Length of output: 1501


🏁 Script executed:

# Search for get_deserialized to understand its return type
rg "get_deserialized" --type rust -B 2 -A 2

Repository: morph-l2/morph-reth

Length of output: 421


🏁 Script executed:

# Find the OtherFields type and get_deserialized method definition
rg "struct OtherFields" --type rust -A 5

Repository: morph-l2/morph-reth

Length of output: 45


🏁 Script executed:

# Search for the get_deserialized method implementation
rg "fn get_deserialized" --type rust -B 2 -A 10

Repository: morph-l2/morph-reth

Length of output: 45


🏁 Script executed:

# Also check the import statements to understand the types
head -30 crates/chainspec/src/genesis.rs

Repository: morph-l2/morph-reth

Length of output: 1179


🏁 Script executed:

# OtherFields is from alloy_serde, let's check if it's in the repo
find . -name "*.rs" | xargs grep -l "impl.*OtherFields" | head -5

Repository: morph-l2/morph-reth

Length of output: 96


🏁 Script executed:

# Let's search for documentation or usage patterns of get_deserialized
rg "get_deserialized" --type rust -B 5 -A 5 | head -100

Repository: morph-l2/morph-reth

Length of output: 775


🏁 Script executed:

# Check if there are tests for the MorphChainConfig::try_from
sed -n '120,200p' crates/chainspec/src/genesis.rs

Repository: morph-l2/morph-reth

Length of output: 2074


🏁 Script executed:

# Search for alloy_serde crate definition or get_deserialized method
rg "alloy_serde" --type rust | head -20

Repository: morph-l2/morph-reth

Length of output: 2128


🏁 Script executed:

# Let's check the Cargo.toml to see alloy_serde version
grep -A 5 "alloy-serde\|alloy_serde" Cargo.toml

Repository: morph-l2/morph-reth

Length of output: 251


🏁 Script executed:

# Search for usage of get_deserialized in other files to understand the pattern
find . -name "*.rs" -exec grep -l "get_deserialized" {} \;

Repository: morph-l2/morph-reth

Length of output: 96


🌐 Web query:

alloy-serde 1.1.3 get_deserialized method documentation

💡 Result:

I couldn’t find any get_deserialized method in the alloy-serde 1.1.3 API. The alloy-serde crate docs (crate page and module index) do not list a get_deserialized item. See the crate docs: [1], [2].

If you meant a different crate (or a different version) please tell me which one and I’ll look it up.

Sources:


🏁 Script executed:

# Let's also check if there's error handling testing for malformed morph config
sed -n '150,300p' crates/chainspec/src/genesis.rs

Repository: morph-l2/morph-reth

Length of output: 1270


🏁 Script executed:

# Search for the definition of get_deserialized in the codebase
rg "fn get_deserialized\|get_deserialized.*=" --type rust -B 3 -A 10

Repository: morph-l2/morph-reth

Length of output: 45


🏁 Script executed:

# Check if OtherFields is extended with get_deserialized somewhere
rg "impl.*OtherFields\|trait.*Ext" --type rust -B 2 -A 15

Repository: morph-l2/morph-reth

Length of output: 45


🏁 Script executed:

# Let's check the alloy dependencies more carefully
grep -B 5 -A 10 "alloy-serde" Cargo.toml | head -20

Repository: morph-l2/morph-reth

Length of output: 608


🏁 Script executed:

# Search for where get_deserialized might be defined as an extension
rg "get_deserialized" --type rust -B 10 | head -80

Repository: morph-l2/morph-reth

Length of output: 771


🏁 Script executed:

# Let's check what OtherFields is from alloy_serde - maybe it has get_deserialized
rg "OtherFields" --type rust -B 5 -A 10 | head -150

Repository: morph-l2/morph-reth

Length of output: 8301


🏁 Script executed:

# Check if get_deserialized might be defined as a trait method somewhere
rg "trait\|impl" --type rust crates/chainspec/src/genesis.rs | head -20

Repository: morph-l2/morph-reth

Length of output: 45


🏁 Script executed:

# Let's look at the entire genesis.rs file to understand imports and context
wc -l crates/chainspec/src/genesis.rs

Repository: morph-l2/morph-reth

Length of output: 98


🏁 Script executed:

# Read the entire file to understand structure
cat crates/chainspec/src/genesis.rs

Repository: morph-l2/morph-reth

Length of output: 6231


🏁 Script executed:

# Let's check if there are any tests that verify error handling for malformed morph config
rg "test.*morph\|#\[test\]" crates/chainspec/src/genesis.rs -A 30 | grep -A 30 "malformed\|invalid\|error"

Repository: morph-l2/morph-reth

Length of output: 45


🏁 Script executed:

# Also check if get_deserialized behavior is tested elsewhere
find . -name "*.rs" -exec grep -l "get_deserialized" {} \; | xargs grep -A 10 "get_deserialized" 2>/dev/null | head -100

Repository: morph-l2/morph-reth

Length of output: 312


🏁 Script executed:

# Search for trait extensions or methods on OtherFields
rg "OtherFields\|get_deserialized" --type rust | grep -i "trait\|extend\|impl" | head -20

Repository: morph-l2/morph-reth

Length of output: 45


🏁 Script executed:

# Check if there's an extension trait defined elsewhere in the codebase
find . -name "*.rs" -exec grep -l "trait.*Ext\|fn get_deserialized" {} \; 2>/dev/null

Repository: morph-l2/morph-reth

Length of output: 133


🏁 Script executed:

# Let's look at the alloy-serde source to understand OtherFields
python3 << 'PYTHON_EOF'
import json
import subprocess

# Try to find what the get_deserialized method actually returns
result = subprocess.run(
    ['rg', 'get_deserialized', '--type', 'rust', '-B', '5', '-A', '15'],
    capture_output=True,
    text=True
)

# Print just the method signature and usage
lines = result.stdout.split('\n')
for i, line in enumerate(lines):
    if 'get_deserialized' in line:
        # Print context around it
        start = max(0, i - 3)
        end = min(len(lines), i + 10)
        for j in range(start, end):
            print(lines[j])
        print("---")
PYTHON_EOF

Repository: morph-l2/morph-reth

Length of output: 149


🏁 Script executed:

# Let's check the usage in alt_fee.rs
grep -B 5 -A 10 "get_deserialized" crates/primitives/src/transaction/alt_fee.rs

Repository: morph-l2/morph-reth

Length of output: 45


🏁 Script executed:

# And check the usage in tx.rs
grep -B 5 -A 10 "get_deserialized" crates/revm/src/tx.rs

Repository: morph-l2/morph-reth

Length of output: 45


🏁 Script executed:

# Let's search for any trait definition that might provide get_deserialized
rg "trait\|impl.*OtherFields" --type rust -A 10 | head -100

Repository: morph-l2/morph-reth

Length of output: 45


🏁 Script executed:

# Check if it's imported from elsewhere or if there's a custom implementation
rg "get_deserialized|OtherFieldsExt" --type rust -B 10 -A 5

Repository: morph-l2/morph-reth

Length of output: 1052


🏁 Script executed:

# Let's examine the Cargo.lock or check what version of alloy-serde is actually used
find . -name "Cargo.lock" -exec head -200 {} \; | grep -A 5 "alloy-serde"

Repository: morph-l2/morph-reth

Length of output: 294


🏁 Script executed:

# Let's verify those files actually exist and contain get_deserialized
ls -la crates/primitives/src/transaction/alt_fee.rs crates/revm/src/tx.rs 2>&1 | head -10

Repository: morph-l2/morph-reth

Length of output: 229


🏁 Script executed:

# Search the entire repository for "get_deserialized" definition
rg "fn get_deserialized|get_deserialized\s*(" --type rust -B 2 -A 8 crates/

Repository: morph-l2/morph-reth

Length of output: 193


🏁 Script executed:

# Let's check if this is a method provided by OtherFields from alloy-serde
# by looking at how OtherFields is used in the test
grep -B 20 "OtherFields.*from_str\|serde_json::from_str.*OtherFields" crates/chainspec/src/genesis.rs | head -30

Repository: morph-l2/morph-reth

Length of output: 747


🏁 Script executed:

# Let's fix the regex and search properly
rg "fn get_deserialized" --type rust -B 2 -A 8

Repository: morph-l2/morph-reth

Length of output: 45


🏁 Script executed:

# Check if get_deserialized is defined in this repo or if it's external
rg "get_deserialized" --type rust | wc -l

Repository: morph-l2/morph-reth

Length of output: 64


🏁 Script executed:

# Let's look at what methods OtherFields provides - check if there's a trait
rg "deserialize_as|OtherFields" crates/chainspec/src/genesis.rs | head -20

Repository: morph-l2/morph-reth

Length of output: 830


🏁 Script executed:

# Let's look at what deserialize_as does in OtherFields
rg "deserialize_as" --type rust -B 5 -A 10 | head -100

Repository: morph-l2/morph-reth

Length of output: 1110


🏁 Script executed:

# Check if get_deserialized is perhaps a method that returns Option<Result>
# Let's look at actual trait implementations or check if this code even compiles
# by examining if there are any CI configs or test results
find . -name "*.toml" -path "*/chainspec*" | head -5

Repository: morph-l2/morph-reth

Length of output: 92


🏁 Script executed:

# Let's check if get_deserialized might be a custom method added to OtherFields somewhere in the codebase
rg "impl.*OtherFields\|trait.*OtherFields" --type rust -B 3 -A 20

Repository: morph-l2/morph-reth

Length of output: 45


🏁 Script executed:

# Let's check the Cargo.toml to see what alloy-serde provides
cat crates/chainspec/Cargo.toml

Repository: morph-l2/morph-reth

Length of output: 970


🏁 Script executed:

# Let's see if there's a way to test the malformed config scenario
grep -A 50 "test_extract_morph_chain_config" crates/chainspec/src/genesis.rs

Repository: morph-l2/morph-reth

Length of output: 1226


🏁 Script executed:

# Let's look for any error case tests for MorphChainConfig
grep -n "Err\|error\|malformed\|invalid" crates/chainspec/src/genesis.rs

Repository: morph-l2/morph-reth

Length of output: 515


Preserve deserialization errors for morph config.

When morph is present but malformed, the if let Some(Ok(...)) pattern fails to match Some(Err(...)), causing the code to fall through to the else branch and report "missing_field" instead of the actual deserialization error.

🛠️ Suggested fix
-        if let Some(Ok(morph_config)) = others.get_deserialized::<Self>("morph") {
-            Ok(morph_config)
-        } else {
-            Err(serde_json::Error::missing_field("morph"))
-        }
+        match others.get_deserialized::<Self>("morph") {
+            Some(Ok(morph_config)) => Ok(morph_config),
+            Some(Err(err)) => Err(err),
+            None => Err(serde_json::Error::missing_field("morph")),
+        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fn try_from(others: &OtherFields) -> Result<Self, Self::Error> {
if let Some(Ok(morph_config)) = others.get_deserialized::<Self>("morph") {
Ok(morph_config)
} else {
Err(serde_json::Error::missing_field("morph"))
}
fn try_from(others: &OtherFields) -> Result<Self, Self::Error> {
match others.get_deserialized::<Self>("morph") {
Some(Ok(morph_config)) => Ok(morph_config),
Some(Err(err)) => Err(err),
None => Err(serde_json::Error::missing_field("morph")),
}
}
🤖 Prompt for AI Agents
In `@crates/chainspec/src/genesis.rs` around lines 113 - 118, The try_from
function currently treats any non-Ok result as a missing field; change the
matching on others.get_deserialized::<Self>("morph") so that it explicitly
handles three cases: Some(Ok(morph_config)) -> return Ok(morph_config);
Some(Err(e)) -> return Err(e) (preserving and propagating the deserialization
error from get_deserialized); None -> return
Err(serde_json::Error::missing_field("morph")). Update the match in try_from
(referencing try_from, OtherFields, get_deserialized::<Self>, and the "morph"
field) to implement these branches so malformed morph JSON yields its original
error instead of a missing_field error.

}
}

#[cfg(test)]
mod tests {
use super::*;
use alloy_primitives::address;

#[test]
fn test_extract_morph_hardfork_info() {
let genesis_info = r#"
{
"bernoulliBlock": 0,
"curieBlock": 100,
"morph203Time": 3000,
"viridianTime": 4000,
"emeraldTime": 5000
}
"#;

let others: OtherFields = serde_json::from_str(genesis_info).unwrap();
let hardfork_info = MorphHardforkInfo::extract_from(&others).unwrap();

assert_eq!(
hardfork_info,
MorphHardforkInfo {
bernoulli_block: Some(0),
curie_block: Some(100),
morph203_time: Some(3000),
viridian_time: Some(4000),
emerald_time: Some(5000),
}
);
}

#[test]
fn test_extract_morph_chain_config() {
let config_str = r#"
{
"morph": {
"feeVaultAddress": "0x530000000000000000000000000000000000000a",
"maxTxPayloadBytesPerBlock": 122880
}
}
"#;

let others: OtherFields = serde_json::from_str(config_str).unwrap();
let config = MorphChainConfig::extract_from(&others).unwrap();

assert_eq!(
config.fee_vault_address,
Some(address!("530000000000000000000000000000000000000a"))
);
assert_eq!(config.max_tx_payload_bytes_per_block, Some(122880));
assert!(config.is_fee_vault_enabled());
assert!(config.is_valid_block_size(100000));
assert!(!config.is_valid_block_size(200000));
}

#[test]
fn test_default_config() {
let config = MorphChainConfig::default();
assert!(!config.is_fee_vault_enabled());
assert_eq!(config.fee_vault_address, None);
assert_eq!(config.max_tx_payload_bytes_per_block, None);
// Without max size limit, any size is valid
assert!(config.is_valid_block_size(usize::MAX));
}
}
Loading