diff --git a/Cargo.lock b/Cargo.lock index 3be97958e..2ba866a41 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3542,6 +3542,7 @@ version = "0.2.0" dependencies = [ "anyhow", "borsh", + "serde", "sov-chain-state", "sov-data-generators", "sov-modules-api", diff --git a/examples/demo-prover/methods/guest-celestia/Cargo.lock b/examples/demo-prover/methods/guest-celestia/Cargo.lock index e377f11e3..b4574b135 100644 --- a/examples/demo-prover/methods/guest-celestia/Cargo.lock +++ b/examples/demo-prover/methods/guest-celestia/Cargo.lock @@ -384,6 +384,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60f6d271ca33075c88028be6f04d502853d63a5ece419d269c15315d4fc1cf1d" dependencies = [ "pkcs8", + "serde", "signature", ] @@ -408,6 +409,7 @@ checksum = "7277392b266383ef8396db7fdeb1e77b6c52fed775f5df15bb24f35b72156980" dependencies = [ "curve25519-dalek", "ed25519", + "serde", "sha2 0.10.6", ] @@ -1274,6 +1276,7 @@ version = "0.2.0" dependencies = [ "anyhow", "borsh", + "serde", "sov-modules-api", "sov-state", "thiserror", @@ -1285,6 +1288,7 @@ version = "0.2.0" dependencies = [ "anyhow", "borsh", + "serde", "sov-modules-api", "sov-rollup-interface", "sov-state", @@ -1374,6 +1378,7 @@ dependencies = [ "borsh", "derive_more", "ed25519-dalek", + "hex", "jmt", "serde", "sha2 0.10.6", @@ -1419,6 +1424,7 @@ version = "0.2.0" dependencies = [ "anyhow", "borsh", + "serde", "sov-modules-api", "sov-modules-macros", "sov-state", @@ -1459,6 +1465,7 @@ version = "0.2.0" dependencies = [ "anyhow", "borsh", + "serde", "sov-bank", "sov-modules-api", "sov-state", @@ -1500,6 +1507,7 @@ version = "0.2.0" dependencies = [ "anyhow", "borsh", + "serde", "sov-modules-api", "sov-state", "thiserror", @@ -1846,8 +1854,3 @@ dependencies = [ "quote", "syn 2.0.37", ] - -[[patch.unused]] -name = "cc" -version = "1.0.79" -source = "git+https://github.com/rust-lang/cc-rs?rev=e5bbdfa#e5bbdfa1fa468c028cb38fee6c35a3cf2e5a2736" diff --git a/examples/demo-prover/methods/guest-mock/Cargo.lock b/examples/demo-prover/methods/guest-mock/Cargo.lock index 7ad1f92f1..d4302d1f0 100644 --- a/examples/demo-prover/methods/guest-mock/Cargo.lock +++ b/examples/demo-prover/methods/guest-mock/Cargo.lock @@ -309,6 +309,7 @@ version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60f6d271ca33075c88028be6f04d502853d63a5ece419d269c15315d4fc1cf1d" dependencies = [ + "serde", "signature", ] @@ -320,6 +321,7 @@ checksum = "7277392b266383ef8396db7fdeb1e77b6c52fed775f5df15bb24f35b72156980" dependencies = [ "curve25519-dalek", "ed25519", + "serde", "sha2", ] @@ -860,6 +862,7 @@ version = "0.2.0" dependencies = [ "anyhow", "borsh", + "serde", "sov-modules-api", "sov-state", "thiserror", @@ -871,6 +874,7 @@ version = "0.2.0" dependencies = [ "anyhow", "borsh", + "serde", "sov-modules-api", "sov-rollup-interface", "sov-state", @@ -933,6 +937,7 @@ dependencies = [ "borsh", "derive_more", "ed25519-dalek", + "hex", "jmt", "serde", "sha2", @@ -978,6 +983,7 @@ version = "0.2.0" dependencies = [ "anyhow", "borsh", + "serde", "sov-modules-api", "sov-modules-macros", "sov-state", @@ -1019,6 +1025,7 @@ version = "0.2.0" dependencies = [ "anyhow", "borsh", + "serde", "sov-bank", "sov-modules-api", "sov-state", @@ -1060,6 +1067,7 @@ version = "0.2.0" dependencies = [ "anyhow", "borsh", + "serde", "sov-modules-api", "sov-state", "thiserror", @@ -1264,8 +1272,3 @@ name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - -[[patch.unused]] -name = "cc" -version = "1.0.79" -source = "git+https://github.com/rust-lang/cc-rs?rev=e5bbdfa#e5bbdfa1fa468c028cb38fee6c35a3cf2e5a2736" diff --git a/examples/demo-rollup/tests/evm/mod.rs b/examples/demo-rollup/tests/evm/mod.rs index cc867c47a..cebe97a4d 100644 --- a/examples/demo-rollup/tests/evm/mod.rs +++ b/examples/demo-rollup/tests/evm/mod.rs @@ -92,17 +92,15 @@ impl TestClient { contract_address: H160, set_arg: u32, ) -> PendingTransaction<'_, Http> { - let nonce = self.eth_get_transaction_count(self.from_addr).await; - + // Tx without gas_limit should estimate and include it in send_transaction endpoint + // Tx without nonce should fetch and include it in send_transaction endpoint let req = Eip1559TransactionRequest::new() .from(self.from_addr) .to(contract_address) .chain_id(self.chain_id) - .nonce(nonce) .data(self.contract.set_call_data(set_arg)) .max_priority_fee_per_gas(10u64) - .max_fee_per_gas(MAX_FEE_PER_GAS) - .gas(900000u64); + .max_fee_per_gas(MAX_FEE_PER_GAS); let typed_transaction = TypedTransaction::Eip1559(req); @@ -148,9 +146,17 @@ impl TestClient { .chain_id(self.chain_id) .nonce(nonce) .data(self.contract.set_call_data(set_arg)) - .gas_price(10u64) - .gas(900000u64); + .gas_price(10u64); + + let typed_transaction = TypedTransaction::Legacy(req.clone()); + // Estimate gas on rpc + let gas = self + .eth_estimate_gas(typed_transaction, Some("latest".to_owned())) + .await; + + // Call with the estimated gas + let req = req.gas(gas); let typed_transaction = TypedTransaction::Legacy(req); let response = self @@ -268,6 +274,16 @@ impl TestClient { .map_err(|e| e.into()) } + async fn eth_estimate_gas(&self, tx: TypedTransaction, block_number: Option) -> u64 { + let gas: ethereum_types::U64 = self + .http_client + .request("eth_estimateGas", rpc_params![tx, block_number]) + .await + .unwrap(); + + gas.as_u64() + } + async fn execute(self) -> Result<(), Box> { // Nonce should be 0 in genesis let nonce = self.eth_get_transaction_count(self.from_addr).await; diff --git a/examples/simple-nft-module/Cargo.toml b/examples/simple-nft-module/Cargo.toml index a4d6446c8..651b9b034 100644 --- a/examples/simple-nft-module/Cargo.toml +++ b/examples/simple-nft-module/Cargo.toml @@ -12,7 +12,7 @@ publish = false [dependencies] anyhow = { workspace = true } borsh = { workspace = true, features = ["rc"] } -serde = { workspace = true, optional = true } +serde = { workspace = true } sov-modules-api = { path = "../../module-system/sov-modules-api" } sov-state = { path = "../../module-system/sov-state" } @@ -27,6 +27,5 @@ simple-nft-module = { version = "*", features = ["native"], path = "." } [features] default = [] -serde = ["dep:serde"] -native = ["serde", "sov-state/native", "sov-modules-api/native", "jsonrpsee"] +native = ["sov-state/native", "sov-modules-api/native", "jsonrpsee"] test = ["native"] diff --git a/examples/simple-nft-module/src/lib.rs b/examples/simple-nft-module/src/lib.rs index 70ea4b156..16bce9c15 100644 --- a/examples/simple-nft-module/src/lib.rs +++ b/examples/simple-nft-module/src/lib.rs @@ -8,6 +8,7 @@ mod genesis; mod query; #[cfg(feature = "native")] pub use query::*; +use serde::{Deserialize, Serialize}; use sov_modules_api::{CallResponse, Context, Error, Module, ModuleInfo, WorkingSet}; #[derive(ModuleInfo, Clone)] @@ -29,6 +30,7 @@ pub struct NonFungibleToken { /// Config for the NonFungibleToken module. /// Sets admin and existing owners. +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct NonFungibleTokenConfig { /// Admin of the NonFungibleToken module. pub admin: C::Address, diff --git a/full-node/sov-ethereum/src/lib.rs b/full-node/sov-ethereum/src/lib.rs index b34e57cb3..264e5420a 100644 --- a/full-node/sov-ethereum/src/lib.rs +++ b/full-node/sov-ethereum/src/lib.rs @@ -18,9 +18,9 @@ pub mod experimental { use jsonrpsee::types::ErrorObjectOwned; use jsonrpsee::RpcModule; use reth_primitives::{ - Address as RethAddress, TransactionSignedNoHash as RethTransactionSignedNoHash, + Address as RethAddress, TransactionSignedNoHash as RethTransactionSignedNoHash, U128, U256, }; - use reth_rpc_types::{TransactionRequest, TypedTransactionRequest}; + use reth_rpc_types::{CallRequest, TransactionRequest, TypedTransactionRequest}; use sov_evm::{CallMessage, Evm, RlpEvmTransaction}; use sov_modules_api::transaction::Transaction; use sov_modules_api::utils::to_jsonrpsee_error_object; @@ -211,6 +211,8 @@ pub mod experimental { let raw_evm_tx = { let mut working_set = WorkingSet::::new(ethereum.storage.clone()); + + // set nonce if none if transaction_request.nonce.is_none() { let nonce = evm .get_transaction_count(from, None, &mut working_set) @@ -219,27 +221,43 @@ pub mod experimental { transaction_request.nonce = Some(nonce); } + // get current chain id let chain_id = evm .chain_id(&mut working_set) .expect("Failed to get chain id") .map(|id| id.as_u64()) .unwrap_or(1); - // TODO: implement gas logic after gas estimation (#906) is implemented - // https://github.com/Sovereign-Labs/sovereign-sdk/issues/906 + // get call request to estimate gas and gas prices + let (call_request, gas_price, max_fee_per_gas) = + get_call_request_and_params(from, chain_id, &transaction_request); + + // estimate gas limit + let gas_limit = U256::from( + evm.eth_estimate_gas(call_request, None, &mut working_set)? + .as_u64(), + ); + + // get typed transaction request let transaction_request = match transaction_request.into_typed_request() { Some(TypedTransactionRequest::Legacy(mut m)) => { m.chain_id = Some(chain_id); + m.gas_limit = gas_limit; + m.gas_price = gas_price; TypedTransactionRequest::Legacy(m) } Some(TypedTransactionRequest::EIP2930(mut m)) => { m.chain_id = chain_id; + m.gas_limit = gas_limit; + m.gas_price = gas_price; TypedTransactionRequest::EIP2930(m) } Some(TypedTransactionRequest::EIP1559(mut m)) => { m.chain_id = chain_id; + m.gas_limit = gas_limit; + m.max_fee_per_gas = max_fee_per_gas; TypedTransactionRequest::EIP1559(m) } @@ -251,10 +269,12 @@ pub mod experimental { } }; + // get raw transaction let transaction = into_transaction(transaction_request).map_err(|_| { to_jsonrpsee_error_object("Invalid types in transaction request", ETH_RPC_ERROR) })?; + // sign transaction let signed_tx = ethereum .eth_rpc_config .eth_signer @@ -338,4 +358,56 @@ pub mod experimental { let bytes: [u8; 16] = bytes[16..].try_into()?; Ok(u128::from_be_bytes(bytes)) } + + fn get_call_request_and_params( + from: reth_primitives::H160, + chain_id: u64, + transaction_request: &TransactionRequest, + ) -> (CallRequest, U128, U128) { + // TODO: we need an oracle to fetch the gas price of the current chain + // https://github.com/Sovereign-Labs/sovereign-sdk/issues/883 + let gas_price = transaction_request.gas_price.unwrap_or_default(); + let max_fee_per_gas = transaction_request.max_fee_per_gas.unwrap_or_default(); + + // TODO: Generate call request better according to the transaction type + // https://github.com/Sovereign-Labs/sovereign-sdk/issues/946 + let call_request = CallRequest { + from: Some(from), + to: transaction_request.to, + gas: transaction_request.gas, + gas_price: { + if transaction_request.max_priority_fee_per_gas.is_some() { + // eip 1559 + None + } else { + // legacy + Some(U256::from(gas_price)) + } + }, + max_fee_per_gas: Some(U256::from(max_fee_per_gas)), + value: transaction_request.value, + input: transaction_request.data.clone().into(), + nonce: transaction_request.nonce, + chain_id: Some(chain_id.into()), + access_list: transaction_request.access_list.clone(), + max_priority_fee_per_gas: { + if transaction_request.max_priority_fee_per_gas.is_some() { + // eip 1559 + Some(U256::from( + transaction_request + .max_priority_fee_per_gas + .unwrap_or(max_fee_per_gas), + )) + } else { + // legacy + None + } + }, + transaction_type: None, + blob_versioned_hashes: vec![], + max_fee_per_blob_gas: None, + }; + + (call_request, gas_price, max_fee_per_gas) + } } diff --git a/module-system/module-implementations/examples/sov-value-setter/Cargo.toml b/module-system/module-implementations/examples/sov-value-setter/Cargo.toml index 392fbcf4a..0b11abf11 100644 --- a/module-system/module-implementations/examples/sov-value-setter/Cargo.toml +++ b/module-system/module-implementations/examples/sov-value-setter/Cargo.toml @@ -22,7 +22,7 @@ anyhow = { workspace = true } sov-modules-api = { path = "../../../sov-modules-api" } sov-state = { path = "../../../sov-state" } schemars = { workspace = true, optional = true } -serde = { workspace = true, optional = true } +serde = { workspace = true } serde_json = { workspace = true, optional = true } thiserror = { workspace = true } borsh = { workspace = true, features = ["rc"] } @@ -31,4 +31,4 @@ clap = { workspace = true, optional = true } [features] default = [] -native = ["serde", "serde_json", "jsonrpsee", "schemars", "clap", "sov-modules-api/native", "sov-state/native"] +native = ["serde_json", "jsonrpsee", "schemars", "clap", "sov-modules-api/native", "sov-state/native"] diff --git a/module-system/module-implementations/examples/sov-value-setter/src/lib.rs b/module-system/module-implementations/examples/sov-value-setter/src/lib.rs index a49519240..a66addb13 100644 --- a/module-system/module-implementations/examples/sov-value-setter/src/lib.rs +++ b/module-system/module-implementations/examples/sov-value-setter/src/lib.rs @@ -15,10 +15,7 @@ pub use query::*; use sov_modules_api::{Error, ModuleInfo, WorkingSet}; /// Initial configuration for sov-value-setter module. -#[cfg_attr( - feature = "native", - derive(serde::Serialize, serde::Deserialize, Debug, PartialEq) -)] +#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq)] pub struct ValueSetterConfig { /// Admin of the module. pub admin: C::Address, diff --git a/module-system/module-implementations/examples/sov-vec-setter/Cargo.toml b/module-system/module-implementations/examples/sov-vec-setter/Cargo.toml index 0f3d77719..5adfa4f79 100644 --- a/module-system/module-implementations/examples/sov-vec-setter/Cargo.toml +++ b/module-system/module-implementations/examples/sov-vec-setter/Cargo.toml @@ -22,7 +22,7 @@ sov-modules-api = { path = "../../../sov-modules-api", default-features = false, sov-state = { path = "../../../sov-state", default-features = false } sov-rollup-interface = { path = "../../../../rollup-interface" } schemars = { workspace = true, optional = true } -serde = { workspace = true, optional = true } +serde = { workspace = true } serde_json = { workspace = true, optional = true } thiserror = { workspace = true } borsh = { workspace = true, features = ["rc"] } @@ -31,4 +31,4 @@ clap = { workspace = true, optional = true } [features] default = ["native"] -native = ["serde", "serde_json", "jsonrpsee", "schemars", "clap", "sov-modules-api/native"] +native = ["serde_json", "jsonrpsee", "schemars", "clap", "sov-modules-api/native"] diff --git a/module-system/module-implementations/examples/sov-vec-setter/src/lib.rs b/module-system/module-implementations/examples/sov-vec-setter/src/lib.rs index 7a74628eb..a444dffa4 100644 --- a/module-system/module-implementations/examples/sov-vec-setter/src/lib.rs +++ b/module-system/module-implementations/examples/sov-vec-setter/src/lib.rs @@ -9,9 +9,11 @@ mod query; pub use call::CallMessage; #[cfg(feature = "native")] pub use query::*; +use serde::{Deserialize, Serialize}; use sov_modules_api::{Error, ModuleInfo, WorkingSet}; /// Initial configuration for sov-vec-setter module. +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct VecSetterConfig { /// Admin of the module. pub admin: C::Address, diff --git a/module-system/module-implementations/integration-tests/Cargo.toml b/module-system/module-implementations/integration-tests/Cargo.toml index 269e24e59..27fc047d3 100644 --- a/module-system/module-implementations/integration-tests/Cargo.toml +++ b/module-system/module-implementations/integration-tests/Cargo.toml @@ -15,6 +15,7 @@ resolver = "2" anyhow = { workspace = true } borsh = { workspace = true, features = ["rc"] } tempfile = { workspace = true } +serde = { workspace = true } sov-modules-api = { path = "../../sov-modules-api", features = ["native"] } sov-state = { path = "../../sov-state", features = ["native"] } diff --git a/module-system/module-implementations/module-template/Cargo.toml b/module-system/module-implementations/module-template/Cargo.toml index c6ca48549..38974ba2d 100644 --- a/module-system/module-implementations/module-template/Cargo.toml +++ b/module-system/module-implementations/module-template/Cargo.toml @@ -16,7 +16,7 @@ anyhow = { workspace = true } borsh = { workspace = true, features = ["rc"] } thiserror = { workspace = true } schemars = { workspace = true, optional = true } -serde = { workspace = true, optional = true } +serde = { workspace = true } serde_json = { workspace = true, optional = true } sov-bank = { path = "../sov-bank" } @@ -31,4 +31,4 @@ module-template = { path = ".", version = "*", features = ["native"] } [features] default = [] -native = ["serde", "serde_json", "schemars", "sov-modules-api/native"] +native = ["serde_json", "schemars", "sov-modules-api/native"] diff --git a/module-system/module-implementations/module-template/src/lib.rs b/module-system/module-implementations/module-template/src/lib.rs index 1daf9e8cf..00c63a402 100644 --- a/module-system/module-implementations/module-template/src/lib.rs +++ b/module-system/module-implementations/module-template/src/lib.rs @@ -5,8 +5,10 @@ mod query; pub use call::CallMessage; #[cfg(feature = "native")] pub use query::*; +use serde::{Deserialize, Serialize}; use sov_modules_api::{Error, ModuleInfo, WorkingSet}; +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct ExampleModuleConfig {} /// A new module: diff --git a/module-system/module-implementations/sov-accounts/Cargo.toml b/module-system/module-implementations/sov-accounts/Cargo.toml index 35ab123d6..fb0aac286 100644 --- a/module-system/module-implementations/sov-accounts/Cargo.toml +++ b/module-system/module-implementations/sov-accounts/Cargo.toml @@ -16,7 +16,7 @@ anyhow = { workspace = true } arbitrary = { workspace = true, optional = true } borsh = { workspace = true, features = ["rc"] } schemars = { workspace = true, optional = true } -serde = { workspace = true, optional = true } +serde = { workspace = true } serde_json = { workspace = true, optional = true } thiserror = { workspace = true } clap = { workspace = true, optional = true } @@ -33,4 +33,4 @@ tempfile = { workspace = true } [features] default = [] arbitrary = ["dep:arbitrary", "sov-state/arbitrary", "sov-modules-api/arbitrary"] -native = ["serde", "serde_json", "jsonrpsee", "schemars", "clap", "sov-state/native", "sov-modules-api/native"] +native = ["serde_json", "jsonrpsee", "schemars", "clap", "sov-state/native", "sov-modules-api/native"] diff --git a/module-system/module-implementations/sov-accounts/src/lib.rs b/module-system/module-implementations/sov-accounts/src/lib.rs index 9f9c987b3..922fce9f4 100644 --- a/module-system/module-implementations/sov-accounts/src/lib.rs +++ b/module-system/module-implementations/sov-accounts/src/lib.rs @@ -15,7 +15,8 @@ pub use call::{CallMessage, UPDATE_ACCOUNT_MSG}; use sov_modules_api::{Context, Error, ModuleInfo, WorkingSet}; /// Initial configuration for sov-accounts module. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[serde(bound = "C::PublicKey: serde::Serialize + serde::de::DeserializeOwned")] pub struct AccountConfig { /// Public keys to initialize the rollup. pub pub_keys: Vec, diff --git a/module-system/module-implementations/sov-bank/Cargo.toml b/module-system/module-implementations/sov-bank/Cargo.toml index 03906f897..18a95f343 100644 --- a/module-system/module-implementations/sov-bank/Cargo.toml +++ b/module-system/module-implementations/sov-bank/Cargo.toml @@ -17,7 +17,7 @@ borsh = { workspace = true, features = ["rc"] } clap = { workspace = true, optional = true, features = ["derive"] } jsonrpsee = { workspace = true, features = ["macros", "client-core", "server"], optional = true } schemars = { workspace = true, optional = true } -serde = { workspace = true, optional = true } +serde = { workspace = true } serde_json = { workspace = true, optional = true } thiserror = { workspace = true } @@ -32,5 +32,5 @@ tempfile = { workspace = true } [features] default = [] -native = ["serde", "serde_json", "jsonrpsee", "clap", "schemars", "sov-state/native", "sov-modules-api/native", ] +native = ["serde_json", "jsonrpsee", "clap", "schemars", "sov-state/native", "sov-modules-api/native", ] cli = ["native"] diff --git a/module-system/module-implementations/sov-bank/src/lib.rs b/module-system/module-implementations/sov-bank/src/lib.rs index d38676fce..7f3cece42 100644 --- a/module-system/module-implementations/sov-bank/src/lib.rs +++ b/module-system/module-implementations/sov-bank/src/lib.rs @@ -12,6 +12,7 @@ pub mod utils; /// Specifies the call methods using in that module. pub use call::CallMessage; +use serde::{Deserialize, Serialize}; use sov_modules_api::{CallResponse, Error, GasUnit, ModuleInfo, WorkingSet}; use token::Token; /// Specifies an interface to interact with tokens. @@ -21,6 +22,7 @@ pub use utils::{get_genesis_token_address, get_token_address}; /// [`TokenConfig`] specifies a configuration used when generating a token for the bank /// module. +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct TokenConfig { /// The name of the token. pub token_name: String, @@ -33,6 +35,7 @@ pub struct TokenConfig { } /// Initial configuration for sov-bank module. +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct BankConfig { /// A list of configurations for the initial tokens. pub tokens: Vec>, diff --git a/module-system/module-implementations/sov-bank/src/token.rs b/module-system/module-implementations/sov-bank/src/token.rs index c552d1b4f..8ad62943a 100644 --- a/module-system/module-implementations/sov-bank/src/token.rs +++ b/module-system/module-implementations/sov-bank/src/token.rs @@ -6,6 +6,7 @@ use std::fmt::Formatter; use std::num::ParseIntError; use anyhow::{bail, Context, Result}; +use serde::{Deserialize, Serialize}; use sov_modules_api::WorkingSet; use sov_state::Prefix; #[cfg(feature = "native")] @@ -21,13 +22,13 @@ pub type Amount = u64; /// (type [`sov_modules_api::Spec::Address`]). #[cfg_attr( feature = "native", - derive(serde::Serialize), - derive(serde::Deserialize), derive(clap::Parser), derive(schemars::JsonSchema), schemars(bound = "C::Address: ::schemars::JsonSchema", rename = "Coins") )] -#[derive(borsh::BorshDeserialize, borsh::BorshSerialize, Debug, PartialEq, Clone)] +#[derive( + borsh::BorshDeserialize, borsh::BorshSerialize, Debug, PartialEq, Clone, Serialize, Deserialize, +)] pub struct Coins { /// An `amount` of coins stored. pub amount: Amount, diff --git a/module-system/module-implementations/sov-chain-state/src/lib.rs b/module-system/module-implementations/sov-chain-state/src/lib.rs index f7bcce225..4de0fbe14 100644 --- a/module-system/module-implementations/sov-chain-state/src/lib.rs +++ b/module-system/module-implementations/sov-chain-state/src/lib.rs @@ -147,6 +147,7 @@ pub struct ChainState } /// Initial configuration of the chain state +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct ChainStateConfig { /// Initial slot height pub initial_slot_height: TransitionHeight, diff --git a/module-system/module-implementations/sov-evm/src/lib.rs b/module-system/module-implementations/sov-evm/src/lib.rs index ea65e39be..a53d197aa 100644 --- a/module-system/module-implementations/sov-evm/src/lib.rs +++ b/module-system/module-implementations/sov-evm/src/lib.rs @@ -47,8 +47,12 @@ mod experimental { Block, BlockEnv, Receipt, SealedBlock, TransactionSignedAndRecovered, }; + // Gas per transaction not creating a contract. + pub(crate) const MIN_TRANSACTION_GAS: u64 = 21_000u64; + pub(crate) const MIN_CREATE_GAS: u64 = 53_000u64; + /// Evm account. - #[derive(Clone, Debug)] + #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] pub struct AccountData { /// Account address. pub address: Address, @@ -75,7 +79,7 @@ mod experimental { } /// Genesis configuration. - #[derive(Clone, Debug)] + #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] pub struct EvmConfig { /// Genesis accounts. pub data: Vec, diff --git a/module-system/module-implementations/sov-evm/src/query.rs b/module-system/module-implementations/sov-evm/src/query.rs index e2314465a..183cb87f5 100644 --- a/module-system/module-implementations/sov-evm/src/query.rs +++ b/module-system/module-implementations/sov-evm/src/query.rs @@ -1,18 +1,24 @@ +use std::array::TryFromSliceError; + use ethereum_types::U64; use jsonrpsee::core::RpcResult; use reth_primitives::contract::create_address; use reth_primitives::TransactionKind::{Call, Create}; use reth_primitives::{TransactionSignedEcRecovered, U128, U256}; +use revm::primitives::{ + EVMError, ExecutionResult, Halt, InvalidTransaction, TransactTo, KECCAK_EMPTY, +}; use sov_modules_api::macros::rpc_gen; use sov_modules_api::WorkingSet; use tracing::info; use crate::call::get_cfg_env; -use crate::error::rpc::ensure_success; +use crate::error::rpc::{ensure_success, RevertError, RpcInvalidTransactionError}; use crate::evm::db::EvmDb; use crate::evm::primitive_types::{BlockEnv, Receipt, SealedBlock, TransactionSignedAndRecovered}; use crate::evm::{executor, prepare_call_env}; -use crate::Evm; +use crate::experimental::{MIN_CREATE_GAS, MIN_TRANSACTION_GAS}; +use crate::{EthApiError, Evm}; #[rpc_gen(client, server, namespace = "eth")] impl Evm { @@ -260,15 +266,177 @@ impl Evm { } /// Handler for: `eth_estimateGas` - // TODO https://github.com/Sovereign-Labs/sovereign-sdk/issues/502 + // https://github.com/paradigmxyz/reth/blob/main/crates/rpc/rpc/src/eth/api/call.rs#L172 #[rpc_method(name = "estimateGas")] pub fn eth_estimate_gas( &self, - _data: reth_rpc_types::CallRequest, - _block_number: Option, - _working_set: &mut WorkingSet, - ) -> RpcResult { - unimplemented!("eth_estimateGas not implemented") + request: reth_rpc_types::CallRequest, + block_number: Option, + working_set: &mut WorkingSet, + ) -> RpcResult { + info!("evm module: eth_estimateGas"); + let mut block_env = match block_number { + Some(ref block_number) if block_number == "pending" => { + self.block_env.get(working_set).unwrap_or_default().clone() + } + _ => { + let block = self.get_sealed_block_by_number(block_number, working_set); + BlockEnv::from(&block) + } + }; + + let tx_env = prepare_call_env(&block_env, request.clone()).unwrap(); + + let cfg = self.cfg.get(working_set).unwrap_or_default(); + let cfg_env = get_cfg_env(&block_env, cfg, Some(get_cfg_env_template())); + + let request_gas = request.gas; + let request_gas_price = request.gas_price; + let env_gas_limit = block_env.gas_limit; + + // get the highest possible gas limit, either the request's set value or the currently + // configured gas limit + let mut highest_gas_limit = request.gas.unwrap_or(U256::from(env_gas_limit)); + + let account = self.accounts.get(&tx_env.caller, working_set).unwrap(); + + // if the request is a simple transfer we can optimize + if tx_env.data.is_empty() { + if let TransactTo::Call(to) = tx_env.transact_to { + let to_account = self.accounts.get(&to, working_set).unwrap(); + if KECCAK_EMPTY == to_account.info.code_hash { + // simple transfer, check if caller has sufficient funds + let available_funds = account.info.balance; + + if tx_env.value > available_funds { + return Err(RpcInvalidTransactionError::InsufficientFundsForTransfer.into()); + } + return Ok(U64::from(MIN_TRANSACTION_GAS)); + } + } + } + + // check funds of the sender + if tx_env.gas_price > U256::ZERO { + // allowance is (balance - tx.value) / tx.gas_price + let allowance = (account.info.balance - tx_env.value) / tx_env.gas_price; + + if highest_gas_limit > allowance { + // cap the highest gas limit by max gas caller can afford with given gas price + highest_gas_limit = allowance; + } + } + + // if the provided gas limit is less than computed cap, use that + let gas_limit = std::cmp::min(U256::from(tx_env.gas_limit), highest_gas_limit); + block_env.gas_limit = convert_u256_to_u64(gas_limit).unwrap(); + + let evm_db = self.get_db(working_set); + + // execute the call without writing to db + let result = executor::inspect(evm_db, &block_env, tx_env.clone(), cfg_env.clone()); + + // Exceptional case: init used too much gas, we need to increase the gas limit and try + // again + if let Err(EVMError::Transaction(InvalidTransaction::CallerGasLimitMoreThanBlock)) = result + { + // if price or limit was included in the request then we can execute the request + // again with the block's gas limit to check if revert is gas related or not + if request_gas.is_some() || request_gas_price.is_some() { + let evm_db = self.get_db(working_set); + return Err(map_out_of_gas_err(block_env, tx_env, cfg_env, evm_db).into()); + } + } + + let result = result.unwrap(); + + match result.result { + ExecutionResult::Success { .. } => { + // succeeded + } + ExecutionResult::Halt { reason, gas_used } => { + return Err(RpcInvalidTransactionError::halt(reason, gas_used).into()) + } + ExecutionResult::Revert { output, .. } => { + // if price or limit was included in the request then we can execute the request + // again with the block's gas limit to check if revert is gas related or not + return if request_gas.is_some() || request_gas_price.is_some() { + let evm_db = self.get_db(working_set); + Err(map_out_of_gas_err(block_env, tx_env, cfg_env, evm_db).into()) + } else { + // the transaction did revert + Err(RpcInvalidTransactionError::Revert(RevertError::new(output)).into()) + }; + } + } + + // at this point we know the call succeeded but want to find the _best_ (lowest) gas the + // transaction succeeds with. we find this by doing a binary search over the + // possible range NOTE: this is the gas the transaction used, which is less than the + // transaction requires to succeed + let gas_used = result.result.gas_used(); + // the lowest value is capped by the gas it takes for a transfer + let mut lowest_gas_limit = if tx_env.transact_to.is_create() { + MIN_CREATE_GAS + } else { + MIN_TRANSACTION_GAS + }; + let mut highest_gas_limit: u64 = highest_gas_limit.try_into().unwrap_or(u64::MAX); + // pick a point that's close to the estimated gas + let mut mid_gas_limit = std::cmp::min( + gas_used * 3, + ((highest_gas_limit as u128 + lowest_gas_limit as u128) / 2) as u64, + ); + // binary search + while (highest_gas_limit - lowest_gas_limit) > 1 { + let mut tx_env = tx_env.clone(); + tx_env.gas_limit = mid_gas_limit; + + let evm_db = self.get_db(working_set); + let result = executor::inspect(evm_db, &block_env, tx_env.clone(), cfg_env.clone()); + + // Exceptional case: init used too much gas, we need to increase the gas limit and try + // again + if let Err(EVMError::Transaction(InvalidTransaction::CallerGasLimitMoreThanBlock)) = + result + { + // increase the lowest gas limit + lowest_gas_limit = mid_gas_limit; + + // new midpoint + mid_gas_limit = ((highest_gas_limit as u128 + lowest_gas_limit as u128) / 2) as u64; + continue; + } + + let result = result.unwrap(); + match result.result { + ExecutionResult::Success { .. } => { + // cap the highest gas limit with succeeding gas limit + highest_gas_limit = mid_gas_limit; + } + ExecutionResult::Revert { .. } => { + // increase the lowest gas limit + lowest_gas_limit = mid_gas_limit; + } + ExecutionResult::Halt { reason, .. } => { + match reason { + Halt::OutOfGas(_) => { + // increase the lowest gas limit + lowest_gas_limit = mid_gas_limit; + } + err => { + // these should be unreachable because we know the transaction succeeds, + // but we consider these cases an error + return Err(RpcInvalidTransactionError::EvmHalt(err).into()); + } + } + } + } + // new midpoint + mid_gas_limit = ((highest_gas_limit as u128 + lowest_gas_limit as u128) / 2) as u64; + } + + Ok(U64::from(highest_gas_limit)) } /// Handler for: `eth_gasPrice` @@ -388,3 +556,32 @@ pub(crate) fn build_rpc_receipt( .collect(), } } + +fn map_out_of_gas_err( + block_env: BlockEnv, + mut tx_env: revm::primitives::TxEnv, + cfg_env: revm::primitives::CfgEnv, + db: EvmDb<'_, C>, +) -> EthApiError { + let req_gas_limit = tx_env.gas_limit; + tx_env.gas_limit = block_env.gas_limit; + let res = executor::inspect(db, &block_env, tx_env, cfg_env).unwrap(); + match res.result { + ExecutionResult::Success { .. } => { + // transaction succeeded by manually increasing the gas limit to + // highest, which means the caller lacks funds to pay for the tx + RpcInvalidTransactionError::BasicOutOfGas(U256::from(req_gas_limit)).into() + } + ExecutionResult::Revert { output, .. } => { + // reverted again after bumping the limit + RpcInvalidTransactionError::Revert(RevertError::new(output)).into() + } + ExecutionResult::Halt { reason, .. } => RpcInvalidTransactionError::EvmHalt(reason).into(), + } +} + +fn convert_u256_to_u64(u256: reth_primitives::U256) -> Result { + let bytes: [u8; 32] = u256.to_be_bytes(); + let bytes: [u8; 8] = bytes[24..].try_into()?; + Ok(u64::from_be_bytes(bytes)) +} diff --git a/module-system/module-implementations/sov-nft-module/Cargo.toml b/module-system/module-implementations/sov-nft-module/Cargo.toml index c11afd529..a063098d0 100644 --- a/module-system/module-implementations/sov-nft-module/Cargo.toml +++ b/module-system/module-implementations/sov-nft-module/Cargo.toml @@ -12,7 +12,7 @@ publish = false [dependencies] anyhow = { workspace = true } borsh = { workspace = true, features = ["rc"] } -serde = { workspace = true, optional = true } +serde = { workspace = true } schemars = { workspace = true, optional = true } serde_json = { workspace = true, optional = true } jsonrpsee = { workspace = true, features = ["macros", "client-core", "server"], optional = true } @@ -29,6 +29,5 @@ sov-nft-module = { version = "*", features = ["native"], path = "." } [features] default = [] -serde = ["dep:serde"] -native = ["serde", "serde_json", "jsonrpsee", "schemars", "sov-state/native", "sov-modules-api/native", ] +native = ["serde_json", "jsonrpsee", "schemars", "sov-state/native", "sov-modules-api/native", ] test = ["native"] diff --git a/module-system/module-implementations/sov-nft-module/src/lib.rs b/module-system/module-implementations/sov-nft-module/src/lib.rs index 3470fec3f..65df31d34 100644 --- a/module-system/module-implementations/sov-nft-module/src/lib.rs +++ b/module-system/module-implementations/sov-nft-module/src/lib.rs @@ -14,6 +14,7 @@ use nft::*; mod query; #[cfg(feature = "native")] pub use query::*; +use serde::{Deserialize, Serialize}; use sov_modules_api::{CallResponse, Context, Error, Module, ModuleInfo, StateMap, WorkingSet}; /// Utility functions. pub mod utils; @@ -38,6 +39,7 @@ pub struct NonFungibleToken { /// Config for the NonFungibleToken module. /// Sets admin and existing owners. +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct NonFungibleTokenConfig {} impl Module for NonFungibleToken { diff --git a/module-system/module-implementations/sov-prover-incentives/Cargo.toml b/module-system/module-implementations/sov-prover-incentives/Cargo.toml index 11255e5fe..381d4aa60 100644 --- a/module-system/module-implementations/sov-prover-incentives/Cargo.toml +++ b/module-system/module-implementations/sov-prover-incentives/Cargo.toml @@ -22,7 +22,7 @@ anyhow = { workspace = true } borsh = { workspace = true, features = ["rc"] } bincode = { workspace = true } schemars = { workspace = true, optional = true } -serde = { workspace = true, optional = true } +serde = { workspace = true } serde_json = { workspace = true, optional = true } sov-bank = { path = "../sov-bank", version = "0.2" } @@ -32,4 +32,4 @@ sov-state = { path = "../../sov-state", version = "0.2" } [features] default = [] -native = ["serde", "serde_json", "schemars", "sov-state/native", "sov-modules-api/native"] +native = ["serde_json", "schemars", "sov-state/native", "sov-modules-api/native"] diff --git a/module-system/module-implementations/sov-prover-incentives/src/lib.rs b/module-system/module-implementations/sov-prover-incentives/src/lib.rs index 2fa93e06e..44cd7414b 100644 --- a/module-system/module-implementations/sov-prover-incentives/src/lib.rs +++ b/module-system/module-implementations/sov-prover-incentives/src/lib.rs @@ -14,6 +14,7 @@ pub use call::CallMessage; /// The response type used by RPC queries. #[cfg(feature = "native")] pub use query::*; +use serde::{Deserialize, Serialize}; use sov_modules_api::{Context, Error, ModuleInfo, WorkingSet, Zkvm}; use sov_state::codec::BcsCodec; @@ -21,6 +22,7 @@ use sov_state::codec::BcsCodec; /// address of the bonding token, the minimum bond, the commitment to /// the allowed verifier method and a set of initial provers with their /// bonding amount. +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct ProverIncentivesConfig { /// The address of the token to be used for bonding. bonding_token_address: C::Address, diff --git a/module-system/module-implementations/sov-sequencer-registry/Cargo.toml b/module-system/module-implementations/sov-sequencer-registry/Cargo.toml index ff677b951..c073ec2fa 100644 --- a/module-system/module-implementations/sov-sequencer-registry/Cargo.toml +++ b/module-system/module-implementations/sov-sequencer-registry/Cargo.toml @@ -24,7 +24,7 @@ sov-bank = { path = "../sov-bank", version = "0.2" } sov-modules-api = { path = "../../sov-modules-api", version = "0.2" } sov-state = { path = "../../sov-state", version = "0.2" } schemars = { workspace = true, optional = true } -serde = { workspace = true, optional = true } +serde = { workspace = true } serde_json = { workspace = true, optional = true } borsh = { workspace = true, features = ["rc"] } jsonrpsee = { workspace = true, features = ["macros", "client-core", "server"], optional = true } @@ -37,7 +37,6 @@ sov-zk-cycle-utils = { path = "../../../utils/zk-cycle-utils", version = "0.2", bench = ["sov-zk-cycle-macros/bench", "risc0-zkvm", "risc0-zkvm-platform", "sov-zk-cycle-utils"] default = [] native = [ - "serde", "serde_json", "jsonrpsee", "schemars", diff --git a/module-system/module-implementations/sov-sequencer-registry/src/lib.rs b/module-system/module-implementations/sov-sequencer-registry/src/lib.rs index 7df00cb9e..a74447e31 100644 --- a/module-system/module-implementations/sov-sequencer-registry/src/lib.rs +++ b/module-system/module-implementations/sov-sequencer-registry/src/lib.rs @@ -16,6 +16,7 @@ mod query; pub use call::CallMessage; #[cfg(feature = "native")] pub use query::*; +use serde::{Deserialize, Serialize}; use sov_modules_api::{CallResponse, Error, ModuleInfo, StateMap, StateValue, WorkingSet}; use sov_state::codec::BcsCodec; @@ -25,6 +26,7 @@ use sov_state::codec::BcsCodec; /// [`Module::genesis`](sov_modules_api::Module::genesis). /// // TODO: Should we allow multiple sequencers in genesis? +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct SequencerConfig { /// The rollup address of the sequencer. pub seq_rollup_address: C::Address, diff --git a/module-system/sov-modules-api/Cargo.toml b/module-system/sov-modules-api/Cargo.toml index 682dd08d4..6e2e256e9 100644 --- a/module-system/sov-modules-api/Cargo.toml +++ b/module-system/sov-modules-api/Cargo.toml @@ -28,11 +28,11 @@ bech32 = { workspace = true } derive_more = { workspace = true } jmt = { workspace = true } serde_json = { workspace = true, optional = true } -hex = { workspace = true, optional = true } +hex = { workspace = true } clap = { workspace = true, optional = true } schemars = { workspace = true, optional = true, features = [] } -ed25519-dalek = { version = "2.0.0", default-features = false } +ed25519-dalek = { version = "2.0.0", default-features = false, features = ["serde"] } rand = { version = "0.8", optional = true } sov-zk-cycle-macros = { path = "../../utils/zk-cycle-macros", version = "0.2", optional = true } @@ -52,10 +52,8 @@ default = ["macros"] native = [ "serde_json", "rand", - "hex", "schemars", "ed25519-dalek/default", - "ed25519-dalek/serde", "ed25519-dalek/rand_core", "clap", "jsonrpsee", diff --git a/module-system/sov-modules-api/src/default_signature.rs b/module-system/sov-modules-api/src/default_signature.rs index ab7be5a2b..48c886f49 100644 --- a/module-system/sov-modules-api/src/default_signature.rs +++ b/module-system/sov-modules-api/src/default_signature.rs @@ -166,11 +166,8 @@ pub mod private_key { } } -#[cfg_attr( - feature = "native", - derive(serde::Serialize, serde::Deserialize, schemars::JsonSchema) -)] -#[derive(PartialEq, Eq, Clone, Debug)] +#[cfg_attr(feature = "native", derive(schemars::JsonSchema))] +#[derive(PartialEq, Eq, Clone, Debug, serde::Serialize, serde::Deserialize)] pub struct DefaultPublicKey { #[cfg_attr( feature = "native", @@ -257,15 +254,8 @@ impl FromStr for DefaultPublicKey { type Err = anyhow::Error; fn from_str(s: &str) -> Result { - let bytes = hex::decode(s)?; - - let bytes: [u8; PUBLIC_KEY_LENGTH] = bytes - .try_into() - .map_err(|_| anyhow::anyhow!("Invalid public key size"))?; - - let pub_key = DalekPublicKey::from_bytes(&bytes) - .map_err(|_| anyhow::anyhow!("Invalid public key"))?; - Ok(DefaultPublicKey { pub_key }) + let pk_hex = crate::pub_key_hex::PublicKeyHex::try_from(s)?; + pk_hex.try_into() } } diff --git a/module-system/sov-modules-api/src/lib.rs b/module-system/sov-modules-api/src/lib.rs index 1446d0543..38ec31cfd 100644 --- a/module-system/sov-modules-api/src/lib.rs +++ b/module-system/sov-modules-api/src/lib.rs @@ -11,6 +11,7 @@ mod encode; mod error; mod gas; pub mod hooks; +mod pub_key_hex; #[cfg(feature = "macros")] mod reexport_macros; @@ -155,7 +156,16 @@ pub enum NonInstantiable {} /// PublicKey used in the Module System. pub trait PublicKey: - borsh::BorshDeserialize + borsh::BorshSerialize + Eq + Hash + Clone + Debug + Send + Sync + borsh::BorshDeserialize + + borsh::BorshSerialize + + Eq + + Hash + + Clone + + Debug + + Send + + Sync + + Serialize + + for<'a> Deserialize<'a> { fn to_address(&self) -> A; } @@ -212,11 +222,7 @@ pub trait Spec { /// The public key used for digital signatures #[cfg(feature = "native")] - type PublicKey: PublicKey - + Serialize - + for<'a> Deserialize<'a> - + ::schemars::JsonSchema - + FromStr; + type PublicKey: PublicKey + ::schemars::JsonSchema + FromStr; #[cfg(not(feature = "native"))] type PublicKey: PublicKey; diff --git a/module-system/sov-modules-api/src/pub_key_hex.rs b/module-system/sov-modules-api/src/pub_key_hex.rs new file mode 100644 index 000000000..3c5d390c5 --- /dev/null +++ b/module-system/sov-modules-api/src/pub_key_hex.rs @@ -0,0 +1,124 @@ +use derive_more::Display; +use ed25519_dalek::{VerifyingKey as DalekPublicKey, PUBLIC_KEY_LENGTH}; + +/// A hexadecimal representation of a PublicKey. +use crate::default_signature::DefaultPublicKey; +#[derive( + serde::Serialize, + serde::Deserialize, + borsh::BorshDeserialize, + borsh::BorshSerialize, + Debug, + PartialEq, + Clone, + Eq, + Display, +)] +#[serde(try_from = "String", into = "String")] +#[display(fmt = "{}", "hex")] +pub struct PublicKeyHex { + hex: String, +} + +impl TryFrom<&str> for PublicKeyHex { + type Error = anyhow::Error; + + fn try_from(hex: &str) -> Result { + Self::try_from(hex.to_owned()) + } +} + +impl TryFrom for PublicKeyHex { + type Error = anyhow::Error; + + fn try_from(hex: String) -> Result { + if hex.len() & 1 != 0 { + anyhow::bail!("Bad hex conversion: odd input length") + } + + if let Some((index, c)) = hex.chars().enumerate().find(|(_, c)| { + !(matches!(c, '0'..='9' | 'a'..='f') || matches!(c, '0'..='9' | 'A'..='F')) + }) { + anyhow::bail!( + "Bad hex conversion: wrong character `{}` at index {}", + c, + index + ) + } + + Ok(Self { hex }) + } +} + +impl From for String { + fn from(pub_key: PublicKeyHex) -> Self { + pub_key.hex + } +} + +impl From for PublicKeyHex { + fn from(pub_key: DefaultPublicKey) -> Self { + let hex = hex::encode(pub_key.pub_key.as_bytes()); + Self { hex } + } +} + +impl TryFrom for DefaultPublicKey { + type Error = anyhow::Error; + + fn try_from(pub_key: PublicKeyHex) -> Result { + let bytes = hex::decode(pub_key.hex)?; + + let bytes: [u8; PUBLIC_KEY_LENGTH] = bytes + .try_into() + .map_err(|_| anyhow::anyhow!("Invalid public key size"))?; + + let pub_key = DalekPublicKey::from_bytes(&bytes) + .map_err(|_| anyhow::anyhow!("Invalid public key"))?; + + Ok(DefaultPublicKey { pub_key }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::default_signature::private_key::DefaultPrivateKey; + use crate::PrivateKey; + + #[test] + fn test_pub_key_hex() { + let pub_key = DefaultPrivateKey::generate().pub_key(); + let pub_key_hex = PublicKeyHex::try_from(pub_key.clone()).unwrap(); + let converted_pub_key = DefaultPublicKey::try_from(pub_key_hex).unwrap(); + assert_eq!(pub_key, converted_pub_key); + } + + #[test] + fn test_pub_key_hex_str() { + let key = "022e229198d957bf0c0a504e7d7bcec99a1d62cccc7861ed2452676ad0323ad8"; + let pub_key_hex_lower: PublicKeyHex = key.try_into().unwrap(); + let pub_key_hex_upper: PublicKeyHex = key.to_uppercase().try_into().unwrap(); + + let pub_key_lower = DefaultPublicKey::try_from(pub_key_hex_lower).unwrap(); + let pub_key_upper = DefaultPublicKey::try_from(pub_key_hex_upper).unwrap(); + + assert_eq!(pub_key_lower, pub_key_upper) + } + + #[test] + fn test_bad_pub_key_hex_str() { + let key = "022e229198d957Zf0c0a504e7d7bcec99a1d62cccc7861ed2452676ad0323ad8"; + let err = PublicKeyHex::try_from(key).unwrap_err(); + + assert_eq!( + err.to_string(), + "Bad hex conversion: wrong character `Z` at index 14" + ); + + let key = "022"; + let err = PublicKeyHex::try_from(key).unwrap_err(); + + assert_eq!(err.to_string(), "Bad hex conversion: odd input length") + } +} diff --git a/module-system/sov-modules-macros/src/dispatch/genesis.rs b/module-system/sov-modules-macros/src/dispatch/genesis.rs index 2530ee86d..b41306336 100644 --- a/module-system/sov-modules-macros/src/dispatch/genesis.rs +++ b/module-system/sov-modules-macros/src/dispatch/genesis.rs @@ -101,6 +101,7 @@ impl GenesisMacro { quote::quote! { #[doc = "Initial configuration for the rollup."] + #[derive(::serde::Deserialize, ::serde::Serialize)] pub struct GenesisConfig #impl_generics #where_clause{ #(#[doc = "Module configuration"] pub #fields)* }