diff --git a/.changelog/unreleased/bug-fixes/1855-fix-ethbridge-vp.md b/.changelog/unreleased/bug-fixes/1855-fix-ethbridge-vp.md
new file mode 100644
index 0000000000..a3947e1f51
--- /dev/null
+++ b/.changelog/unreleased/bug-fixes/1855-fix-ethbridge-vp.md
@@ -0,0 +1,2 @@
+- Fix the Ethereum Bridge VP
+ ([\#1855](https://github.com/anoma/namada/pull/1855))
\ No newline at end of file
diff --git a/core/src/ledger/eth_bridge/storage/mod.rs b/core/src/ledger/eth_bridge/storage/mod.rs
index b777caa265..e728603fb7 100644
--- a/core/src/ledger/eth_bridge/storage/mod.rs
+++ b/core/src/ledger/eth_bridge/storage/mod.rs
@@ -7,7 +7,7 @@ use super::ADDRESS;
use crate::ledger::parameters::storage::*;
use crate::ledger::parameters::ADDRESS as PARAM_ADDRESS;
use crate::types::address::Address;
-use crate::types::storage::{Key, KeySeg};
+use crate::types::storage::{DbKeySeg, Key, KeySeg};
use crate::types::token::balance_key;
/// Key prefix for the storage subspace
@@ -26,6 +26,15 @@ pub fn escrow_key(nam_addr: &Address) -> Key {
balance_key(nam_addr, &ADDRESS)
}
+/// Check if the given `key` contains an Ethereum
+/// bridge address segment.
+#[inline]
+pub fn has_eth_addr_segment(key: &Key) -> bool {
+ key.segments
+ .iter()
+ .any(|s| matches!(s, DbKeySeg::AddressSeg(ADDRESS)))
+}
+
/// Returns whether a key belongs to this account or not
pub fn is_eth_bridge_key(nam_addr: &Address, key: &Key) -> bool {
key == &escrow_key(nam_addr)
diff --git a/shared/src/ledger/native_vp/ethereum_bridge/vp.rs b/shared/src/ledger/native_vp/ethereum_bridge/vp.rs
index 00ca7d933f..650c065ab7 100644
--- a/shared/src/ledger/native_vp/ethereum_bridge/vp.rs
+++ b/shared/src/ledger/native_vp/ethereum_bridge/vp.rs
@@ -1,22 +1,23 @@
//! Validity predicate for the Ethereum bridge
use std::collections::{BTreeSet, HashSet};
-use borsh::BorshDeserialize;
use eyre::{eyre, Result};
-use itertools::Itertools;
-use namada_core::ledger::eth_bridge::storage::{
- self, escrow_key, wrapped_erc20s,
-};
+use namada_core::ledger::eth_bridge::storage::{self, escrow_key};
use namada_core::ledger::storage::traits::StorageHasher;
use namada_core::ledger::{eth_bridge, storage as ledger_storage};
use namada_core::types::address::Address;
use namada_core::types::storage::Key;
-use namada_core::types::token::{balance_key, Amount};
+use namada_core::types::token::{balance_key, is_balance_key, Amount};
-use crate::ledger::native_vp::{Ctx, NativeVp, VpEnv};
+use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader};
use crate::proto::Tx;
use crate::vm::WasmCacheAccess;
+/// Generic error that may be returned by the validity predicate
+#[derive(thiserror::Error, Debug)]
+#[error(transparent)]
+pub struct Error(#[from] eyre::Error);
+
/// Validity predicate for the Ethereum bridge
pub struct EthBridge<'ctx, DB, H, CA>
where
@@ -34,39 +35,29 @@ where
H: 'static + StorageHasher,
CA: 'static + WasmCacheAccess,
{
- /// If the bridge's escrow key was changed, we check
- /// that the balance increased and that the bridge pool
- /// VP has been triggered. The bridge pool VP will carry
- /// out the rest of the checks.
+ /// If the Ethereum bridge's escrow key was written to, we check
+ /// that the NAM balance increased and that the Bridge pool VP has
+ /// been triggered.
fn check_escrow(
&self,
verifiers: &BTreeSet
,
) -> Result {
let escrow_key =
balance_key(&self.ctx.storage.native_token, ð_bridge::ADDRESS);
- let escrow_pre: Amount = if let Ok(Some(bytes)) =
- self.ctx.read_bytes_pre(&escrow_key)
- {
- BorshDeserialize::try_from_slice(bytes.as_slice()).map_err(
- |_| Error(eyre!("Couldn't deserialize a balance from storage")),
- )?
- } else {
- tracing::debug!(
- "Could not retrieve the Ethereum bridge VP's balance from \
- storage"
- );
- return Ok(false);
- };
+
+ let escrow_pre: Amount =
+ if let Ok(Some(value)) = (&self.ctx).read_pre_value(&escrow_key) {
+ value
+ } else {
+ tracing::debug!(
+ "Could not retrieve the Ethereum bridge VP's balance from \
+ storage"
+ );
+ return Ok(false);
+ };
let escrow_post: Amount =
- if let Ok(Some(bytes)) = self.ctx.read_bytes_post(&escrow_key) {
- BorshDeserialize::try_from_slice(bytes.as_slice()).map_err(
- |_| {
- Error(eyre!(
- "Couldn't deserialize the balance of the Ethereum \
- bridge VP from storage."
- ))
- },
- )?
+ if let Ok(Some(value)) = (&self.ctx).read_post_value(&escrow_key) {
+ value
} else {
tracing::debug!(
"Could not retrieve the modified Ethereum bridge VP's \
@@ -77,6 +68,8 @@ where
// The amount escrowed should increase.
if escrow_pre < escrow_post {
+ // NB: normally, we only escrow NAM under the Ethereum bridge
+ // addresss in the context of a Bridge pool transfer
Ok(verifiers.contains(&storage::bridge_pool::BRIDGE_POOL_ADDRESS))
} else {
tracing::info!(
@@ -88,19 +81,6 @@ where
}
}
-/// One of the the two types of checks
-/// this VP must perform.
-#[derive(Debug)]
-enum CheckType {
- Escrow,
- Erc20Transfer,
-}
-
-#[derive(thiserror::Error, Debug)]
-#[error(transparent)]
-/// Generic error that may be returned by the validity predicate
-pub struct Error(#[from] eyre::Error);
-
impl<'a, DB, H, CA> NativeVp for EthBridge<'a, DB, H, CA>
where
DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>,
@@ -112,12 +92,8 @@ where
/// Validate that a wasm transaction is permitted to change keys under this
/// account.
///
- /// We permit only the following changes via wasm for the time being:
- /// - a wrapped ERC20's supply key to decrease iff one of its balance keys
- /// decreased by the same amount
- /// - a wrapped ERC20's balance key to decrease iff another one of its
- /// balance keys increased by the same amount
- /// - Escrowing Nam in order to mint wrapped Nam on Ethereum
+ /// We only permit increasing the escrowed balance of NAM under the Ethereum
+ /// bridge address, when writing to storage from wasm transactions.
///
/// Some other changes to the storage subspace of this account are expected
/// to happen natively i.e. bypassing this validity predicate. For example,
@@ -135,33 +111,37 @@ where
"Ethereum Bridge VP triggered",
);
- match determine_check_type(
- &self.ctx.storage.native_token,
- keys_changed,
- )? {
- // Multitoken VP checks the balance changes for the ERC20 transfer
- Some(CheckType::Erc20Transfer) => Ok(true),
- Some(CheckType::Escrow) => self.check_escrow(verifiers),
- None => Ok(false),
+ if !validate_changed_keys(&self.ctx.storage.native_token, keys_changed)?
+ {
+ return Ok(false);
}
+
+ self.check_escrow(verifiers)
}
}
/// Checks if `keys_changed` represents a valid set of changed keys.
-/// Depending on which keys get changed, chooses which type of
-/// check to perform in the `validate_tx` function.
-/// 1. If the Ethereum bridge escrow key was changed, we need to check
-/// that escrow was performed correctly.
-/// 2. If two erc20 keys where changed, this is a transfer that needs
-/// to be checked.
-fn determine_check_type(
+///
+/// This implies cheking if two distinct keys were changed:
+///
+/// 1. The Ethereum bridge escrow account's NAM balance key.
+/// 2. Another account's NAM balance key.
+///
+/// Any other keys changed under the Ethereum bridge account
+/// are rejected.
+fn validate_changed_keys(
nam_addr: &Address,
keys_changed: &BTreeSet,
-) -> Result