Skip to content

Commit

Permalink
Validate Bridge pool client transfers
Browse files Browse the repository at this point in the history
  • Loading branch information
sug0 committed Oct 9, 2023
1 parent 65f3717 commit 32cbf1c
Showing 1 changed file with 167 additions and 23 deletions.
190 changes: 167 additions & 23 deletions shared/src/sdk/eth_bridge/bridge_pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ use std::sync::Arc;
use borsh::BorshSerialize;
use ethbridge_bridge_contract::Bridge;
use ethers::providers::Middleware;
use futures::future::FutureExt;
use namada_core::ledger::eth_bridge::storage::bridge_pool::get_pending_key;
use namada_core::ledger::eth_bridge::storage::wrapped_erc20s;
use namada_core::types::ethereum_events::EthAddress;
use namada_core::types::key::common;
Expand All @@ -24,13 +26,15 @@ use crate::ledger::queries::{
use crate::proto::Tx;
use crate::sdk::args;
use crate::sdk::error::{
EncodingError, Error, EthereumBridgeError, QueryError,
EncodingError, Error, EthereumBridgeError, QueryError, TxError,
};
use crate::sdk::masp::{ShieldedContext, ShieldedUtils};
use crate::sdk::rpc::{query_wasm_code_hash, validate_amount};
use crate::sdk::rpc::{
query_storage_value, query_wasm_code_hash, validate_amount,
};
use crate::sdk::tx::prepare_tx;
use crate::sdk::wallet::{Wallet, WalletUtils};
use crate::types::address::Address;
use crate::types::address::{Address, InternalAddress};
use crate::types::control_flow::install_shutdown_signal;
use crate::types::control_flow::time::{Duration, Instant};
use crate::types::eth_abi::Encode;
Expand All @@ -39,7 +43,7 @@ use crate::types::eth_bridge_pool::{
};
use crate::types::io::Io;
use crate::types::keccak::KeccakHash;
use crate::types::token::{Amount, DenominatedAmount};
use crate::types::token::{balance_key, Amount, DenominatedAmount};
use crate::types::voting_power::FractionalVotingPower;
use crate::{display, display_line, edisplay_line};

Expand Down Expand Up @@ -123,26 +127,38 @@ where
C: crate::ledger::queries::Client + Sync,
IO: Io,
{
let DenominatedAmount { amount, .. } = validate_amount::<_, IO>(
client,
amount,
&wrapped_erc20s::token(&asset),
force,
)
.await
.map_err(|e| Error::Other(format!("Failed to validate amount. {}", e)))?;
let token_addr = wrapped_erc20s::token(&asset);
let validate_token_amount =
validate_amount::<_, IO>(client, amount, &token_addr, force).map(
|result| {
result.map_err(|e| {
Error::Other(format!(
"Failed to validate Bridge pool transfer amount: {e}"
))
})
},
);

let DenominatedAmount {
amount: fee_amount, ..
} = validate_amount::<_, IO>(client, fee_amount, &fee_token, force)
.await
.map_err(|e| {
let validate_fee_amount = validate_amount::<_, IO>(
client, fee_amount, &fee_token, force,
)
.map(|result| {
result.map_err(|e| {
Error::Other(format!(
"Failed to validate Bridge pool fee amount. {}",
e
"Failed to validate Bridge pool fee amount: {e}",
))
})?;
})
});

// validate amounts
let (
tok_denominated @ DenominatedAmount { amount, .. },
fee_denominated @ DenominatedAmount {
amount: fee_amount, ..
},
) = futures::try_join!(validate_token_amount, validate_fee_amount)?;

// build pending Bridge pool transfer
let fee_payer = fee_payer.unwrap_or_else(|| sender.clone());
let transfer = PendingTransfer {
transfer: TransferToEthereum {
Expand All @@ -163,9 +179,137 @@ where
},
};

// if force {
// return Ok(transfer);
//}
if force {
return Ok(transfer);
}

//======================================================
// XXX: the following validations should be kept in sync
// with the validations performed by the Bridge pool VP!
//======================================================

// check if an identical transfer is already in the Bridge pool
let transfer_in_pool = RPC
.shell()
.storage_has_key(client, &get_pending_key(&transfer))
.await
.map_err(|e| Error::Query(QueryError::General(e.to_string())))?;
if transfer_in_pool {
return Err(Error::EthereumBridge(
EthereumBridgeError::TransferAlreadyInPool,
));
}

let wnam_addr = RPC
.shell()
.eth_bridge()
.read_native_erc20_contract(client)
.await
.map_err(|e| {
Error::EthereumBridge(EthereumBridgeError::RetrieveContract(
e.to_string(),
))
})?;

// validate gas fee token
match &transfer.gas_fee.token {
Address::Internal(InternalAddress::Nut(_)) => {
return Err(Error::EthereumBridge(
EthereumBridgeError::InvalidFeeToken(transfer.gas_fee.token),
));
}
fee_token if fee_token == &wrapped_erc20s::token(&wnam_addr) => {
return Err(Error::EthereumBridge(
EthereumBridgeError::InvalidFeeToken(transfer.gas_fee.token),
));
}
_ => {}
}

// validate wnam token caps + whitelist
if transfer.transfer.asset == wnam_addr {
let flow_control = RPC
.shell()
.eth_bridge()
.get_erc20_flow_control(client, &wnam_addr)
.await
.map_err(|e| {
Error::Query(QueryError::General(format!(
"Failed to read wrapped NAM flow control data: {e}"
)))
})?;

if !flow_control.whitelisted {
return Err(Error::EthereumBridge(
EthereumBridgeError::Erc20NotWhitelisted(wnam_addr),
));
}

if flow_control.exceeds_token_caps(transfer.transfer.amount) {
return Err(Error::EthereumBridge(
EthereumBridgeError::Erc20TokenCapsExceeded(wnam_addr),
));
}
}

// validate balances
let maybe_balance_error = if token_addr == transfer.gas_fee.token {
let expected_debit = transfer.transfer.amount + transfer.gas_fee.amount;
let balance: Amount = query_storage_value(
client,
&balance_key(&token_addr, &transfer.transfer.sender),
)
.await?;

balance
.checked_sub(expected_debit)
.is_none()
.then_some((token_addr, tok_denominated))
} else {
let check_tokens = async {
let balance: Amount = query_storage_value(
client,
&balance_key(&token_addr, &transfer.transfer.sender),
)
.await?;
Result::<_, Error>::Ok(
balance
.checked_sub(transfer.transfer.amount)
.is_none()
.then_some((token_addr, tok_denominated)),
)
};
let check_fees = async {
let balance: Amount = query_storage_value(
client,
&balance_key(
&transfer.gas_fee.token,
&transfer.transfer.sender,
),
)
.await?;
Result::<_, Error>::Ok(
balance
.checked_sub(transfer.gas_fee.amount)
.is_none()
.then_some((
transfer.gas_fee.token.clone(),
fee_denominated,
)),
)
};

let (err_tokens, err_fees) =
futures::try_join!(check_tokens, check_fees)?;
err_tokens.or(err_fees)
};
if let Some((token, amount)) = maybe_balance_error {
return Err(Error::Tx(TxError::NegativeBalanceAfterTransfer(
Box::new(transfer.transfer.sender),
amount.to_string(),
Box::new(token),
)));
}

Ok(transfer)
}
Expand Down

0 comments on commit 32cbf1c

Please sign in to comment.