diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index fcd57be745c..55b87799838 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -896,9 +896,11 @@ pub fn genesis( } #[cfg(any(test, feature = "dev"))] pub fn genesis(num_validators: u64) -> Genesis { + use namada::ledger::eth_bridge::Erc20WhitelistEntry; use namada::types::address::{ self, apfel, btc, dot, eth, kartoffel, nam, schnitzel, }; + use namada::types::ethereum_events::testing::DAI_ERC20_ETH_ADDRESS; use crate::wallet; @@ -1096,6 +1098,11 @@ pub fn genesis(num_validators: u64) -> Genesis { pos_params: PosParams::default(), gov_params: GovParams::default(), ethereum_bridge_params: Some(EthereumBridgeConfig { + erc20_whitelist: vec![Erc20WhitelistEntry { + token_address: DAI_ERC20_ETH_ADDRESS, + denomination: 18.into(), + token_cap: token::Amount::max(), + }], eth_start_height: Default::default(), min_confirmations: Default::default(), contracts: Contracts { diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index e1b7f088835..1baa0922490 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -24,7 +24,7 @@ use std::rc::Rc; use borsh::{BorshDeserialize, BorshSerialize}; use namada::core::ledger::eth_bridge; -use namada::ledger::eth_bridge::{EthBridgeQueries, EthereumBridgeConfig}; +use namada::ledger::eth_bridge::{EthBridgeQueries, EthereumOracleConfig}; use namada::ledger::events::log::EventLog; use namada::ledger::events::Event; use namada::ledger::gas::BlockGasMeter; @@ -963,27 +963,16 @@ where ); return; } - let Some(config) = EthereumBridgeConfig::read(&self.wl_storage) else { - tracing::info!( - "Not starting oracle as the Ethereum bridge config couldn't be found in storage" - ); - return; - }; + let config = EthereumOracleConfig::read(&self.wl_storage).expect( + "The oracle config must be present in storage, since the \ + bridge is enabled", + ); let start_block = self .wl_storage .storage .ethereum_height .clone() - .unwrap_or_else(|| { - self.wl_storage - .read(ð_bridge::storage::eth_start_height_key()) - .expect( - "Failed to read Ethereum start height from storage", - ) - .expect( - "The Ethereum start height should be in storage", - ) - }); + .unwrap_or(config.eth_start_height); tracing::info!( ?start_block, "Found Ethereum height from which the Ethereum oracle should \ diff --git a/ethereum_bridge/src/parameters.rs b/ethereum_bridge/src/parameters.rs index 4f3b2f1bc52..65ae529cdd4 100644 --- a/ethereum_bridge/src/parameters.rs +++ b/ethereum_bridge/src/parameters.rs @@ -3,6 +3,7 @@ use std::num::NonZeroU64; use borsh::{BorshDeserialize, BorshSerialize}; use eyre::{eyre, Result}; +use namada_core::ledger::eth_bridge::storage::whitelist; use namada_core::ledger::storage; use namada_core::ledger::storage::types::encode; use namada_core::ledger::storage::WlStorage; @@ -10,11 +11,35 @@ use namada_core::ledger::storage_api::{StorageRead, StorageWrite}; use namada_core::types::ethereum_events::EthAddress; use namada_core::types::ethereum_structs; use namada_core::types::storage::Key; +use namada_core::types::token::{Amount, Denomination}; use serde::{Deserialize, Serialize}; -use crate::storage::eth_bridge_queries::{EthBridgeEnabled, EthBridgeStatus}; +use crate::storage::eth_bridge_queries::{ + EthBridgeEnabled, EthBridgeQueries, EthBridgeStatus, +}; use crate::{bridge_pool_vp, storage as bridge_storage, vp}; +/// An ERC20 token whitelist entry. +#[derive( + Clone, + Copy, + Eq, + PartialEq, + Debug, + Deserialize, + Serialize, + BorshSerialize, + BorshDeserialize, +)] +pub struct Erc20WhitelistEntry { + /// The address of the whitelisted ERC20 token. + pub token_address: EthAddress, + /// The denomination of the whitelisted ERC20 token. + pub denomination: Denomination, + /// The token cap of the whitelisted ERC20 token. + pub token_cap: Amount, +} + /// Represents a configuration value for the minimum number of /// confirmations an Ethereum event must reach before it can be acted on. #[derive( @@ -135,6 +160,8 @@ pub struct EthereumBridgeConfig { /// Minimum number of confirmations needed to trust an Ethereum branch. /// This must be at least one. pub min_confirmations: MinimumConfirmations, + /// List of ERC20 token types whitelisted at genesis time. + pub erc20_whitelist: Vec, /// The addresses of the Ethereum contracts that need to be directly known /// by validators. pub contracts: Contracts, @@ -151,6 +178,7 @@ impl EthereumBridgeConfig { H: 'static + storage::traits::StorageHasher, { let Self { + erc20_whitelist, eth_start_height, min_confirmations, contracts: @@ -187,13 +215,72 @@ impl EthereumBridgeConfig { wl_storage .write_bytes(ð_start_height_key, encode(eth_start_height)) .unwrap(); + for Erc20WhitelistEntry { + token_address: addr, + token_cap: cap, + denomination: denom, + } in erc20_whitelist + { + let key = whitelist::Key { + asset: *addr, + suffix: whitelist::KeyType::Whitelisted, + } + .into(); + wl_storage.write_bytes(&key, encode(&true)).unwrap(); + + let key = whitelist::Key { + asset: *addr, + suffix: whitelist::KeyType::Cap, + } + .into(); + wl_storage.write_bytes(&key, encode(cap)).unwrap(); + + let key = whitelist::Key { + asset: *addr, + suffix: whitelist::KeyType::Denomination, + } + .into(); + wl_storage.write_bytes(&key, encode(denom)).unwrap(); + } // Initialize the storage for the Ethereum Bridge VP. vp::init_storage(wl_storage); // Initialize the storage for the Bridge Pool VP. bridge_pool_vp::init_storage(wl_storage); } +} + +/// Subset of [`EthereumBridgeConfig`], containing only Ethereum +/// oracle specific parameters. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct EthereumOracleConfig { + /// Initial Ethereum block height when events will first be extracted from. + pub eth_start_height: ethereum_structs::BlockHeight, + /// Minimum number of confirmations needed to trust an Ethereum branch. + /// This must be at least one. + pub min_confirmations: MinimumConfirmations, + /// The addresses of the Ethereum contracts that need to be directly known + /// by validators. + pub contracts: Contracts, +} + +impl From for EthereumOracleConfig { + fn from(config: EthereumBridgeConfig) -> Self { + let EthereumBridgeConfig { + eth_start_height, + min_confirmations, + contracts, + .. + } = config; + Self { + eth_start_height, + min_confirmations, + contracts, + } + } +} - /// Reads the latest [`EthereumBridgeConfig`] from storage. If it is not +impl EthereumOracleConfig { + /// Reads the latest [`EthereumOracleConfig`] from storage. If it is not /// present, `None` will be returned - this could be the case if the bridge /// has not been bootstrapped yet. Panics if the storage appears to be /// corrupt. @@ -202,25 +289,27 @@ impl EthereumBridgeConfig { DB: 'static + storage::DB + for<'iter> storage::DBIter<'iter>, H: 'static + storage::traits::StorageHasher, { + // TODO: remove present key check; `is_bridge_active` should not + // panic, when the active status key has not been written to; simply + // return bridge disabled instead + let has_active_key = + wl_storage.has_key(&bridge_storage::active_key()).unwrap(); + + if !has_active_key || !wl_storage.ethbridge_queries().is_bridge_active() + { + return None; + } + let min_confirmations_key = bridge_storage::min_confirmations_key(); let native_erc20_key = bridge_storage::native_erc20_key(); let bridge_contract_key = bridge_storage::bridge_contract_key(); let governance_contract_key = bridge_storage::governance_contract_key(); let eth_start_height_key = bridge_storage::eth_start_height_key(); - let Some(min_confirmations) = StorageRead::read::( - wl_storage, - &min_confirmations_key, - ) - .unwrap_or_else(|err| { - panic!("Could not read {min_confirmations_key}: {err:?}") - }) else { - // The bridge has not been configured yet - return None; - }; - // These reads must succeed otherwise the storage is corrupt or a // read failed + let min_confirmations = + must_read_key(wl_storage, &min_confirmations_key); let native_erc20 = must_read_key(wl_storage, &native_erc20_key); let bridge_contract = must_read_key(wl_storage, &bridge_contract_key); let governance_contract = @@ -299,6 +388,7 @@ mod tests { #[test] fn test_round_trip_toml_serde() -> Result<()> { let config = EthereumBridgeConfig { + erc20_whitelist: vec![], eth_start_height: Default::default(), min_confirmations: MinimumConfirmations::default(), contracts: Contracts { @@ -324,6 +414,7 @@ mod tests { fn test_ethereum_bridge_config_read_write_storage() { let mut wl_storage = TestWlStorage::default(); let config = EthereumBridgeConfig { + erc20_whitelist: vec![], eth_start_height: Default::default(), min_confirmations: MinimumConfirmations::default(), contracts: Contracts { @@ -340,7 +431,8 @@ mod tests { }; config.init_storage(&mut wl_storage); - let read = EthereumBridgeConfig::read(&wl_storage).unwrap(); + let read = EthereumOracleConfig::read(&wl_storage).unwrap(); + let config = EthereumOracleConfig::from(config); assert_eq!(config, read); } @@ -348,7 +440,7 @@ mod tests { #[test] fn test_ethereum_bridge_config_uninitialized() { let wl_storage = TestWlStorage::default(); - let read = EthereumBridgeConfig::read(&wl_storage); + let read = EthereumOracleConfig::read(&wl_storage); assert!(read.is_none()); } @@ -358,6 +450,7 @@ mod tests { fn test_ethereum_bridge_config_storage_corrupt() { let mut wl_storage = TestWlStorage::default(); let config = EthereumBridgeConfig { + erc20_whitelist: vec![], eth_start_height: Default::default(), min_confirmations: MinimumConfirmations::default(), contracts: Contracts { @@ -379,7 +472,7 @@ mod tests { .unwrap(); // This should panic because the min_confirmations value is not valid - EthereumBridgeConfig::read(&wl_storage); + EthereumOracleConfig::read(&wl_storage); } #[test] @@ -388,16 +481,21 @@ mod tests { )] fn test_ethereum_bridge_config_storage_partially_configured() { let mut wl_storage = TestWlStorage::default(); + wl_storage + .write_bytes( + &bridge_storage::active_key(), + encode(&EthBridgeStatus::Enabled(EthBridgeEnabled::AtGenesis)), + ) + .unwrap(); // Write a valid min_confirmations value - let min_confirmations_key = bridge_storage::min_confirmations_key(); wl_storage .write_bytes( - &min_confirmations_key, + &bridge_storage::min_confirmations_key(), MinimumConfirmations::default().try_to_vec().unwrap(), ) .unwrap(); // This should panic as the other config values are not written - EthereumBridgeConfig::read(&wl_storage); + EthereumOracleConfig::read(&wl_storage); } } diff --git a/ethereum_bridge/src/test_utils.rs b/ethereum_bridge/src/test_utils.rs index f2a1ee0b8cb..aec64633964 100644 --- a/ethereum_bridge/src/test_utils.rs +++ b/ethereum_bridge/src/test_utils.rs @@ -92,6 +92,8 @@ pub fn bootstrap_ethereum_bridge( wl_storage: &mut TestWlStorage, ) -> EthereumBridgeConfig { let config = EthereumBridgeConfig { + // start with empty erc20 whitelist + erc20_whitelist: vec![], eth_start_height: Default::default(), min_confirmations: MinimumConfirmations::from(unsafe { // SAFETY: The only way the API contract of `NonZeroU64` can @@ -212,6 +214,7 @@ pub fn init_storage_with_validators( ) .expect("Test failed"); let config = EthereumBridgeConfig { + erc20_whitelist: vec![], eth_start_height: Default::default(), min_confirmations: Default::default(), contracts: Contracts { diff --git a/shared/src/ledger/native_vp/ethereum_bridge/bridge_pool_vp.rs b/shared/src/ledger/native_vp/ethereum_bridge/bridge_pool_vp.rs index cc911499528..6b04aa843d2 100644 --- a/shared/src/ledger/native_vp/ethereum_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/native_vp/ethereum_bridge/bridge_pool_vp.rs @@ -542,6 +542,7 @@ mod test_bridge_pool_vp { fn setup_storage() -> WlStorage { // a dummy config for testing let config = EthereumBridgeConfig { + erc20_whitelist: vec![], eth_start_height: Default::default(), min_confirmations: Default::default(), contracts: Contracts { diff --git a/shared/src/ledger/native_vp/ethereum_bridge/vp.rs b/shared/src/ledger/native_vp/ethereum_bridge/vp.rs index f2b0f5245e0..1280e19439c 100644 --- a/shared/src/ledger/native_vp/ethereum_bridge/vp.rs +++ b/shared/src/ledger/native_vp/ethereum_bridge/vp.rs @@ -395,6 +395,7 @@ mod tests { // a dummy config for testing let config = EthereumBridgeConfig { + erc20_whitelist: vec![], eth_start_height: Default::default(), min_confirmations: Default::default(), contracts: Contracts { diff --git a/tests/src/native_vp/eth_bridge_pool.rs b/tests/src/native_vp/eth_bridge_pool.rs index e53a82699af..920f7b8f082 100644 --- a/tests/src/native_vp/eth_bridge_pool.rs +++ b/tests/src/native_vp/eth_bridge_pool.rs @@ -64,6 +64,7 @@ mod test_bridge_pool_vp { ..Default::default() }; let config = EthereumBridgeConfig { + erc20_whitelist: vec![], eth_start_height: Default::default(), min_confirmations: Default::default(), contracts: Contracts {